(outline-up-heading): Add `invisible-ok' arg.
[bpt/emacs.git] / lisp / textmodes / flyspell.el
CommitLineData
e8af40ee 1;;; flyspell.el --- on-the-fly spell checker
60371a2e 2
1f44857f 3;; Copyright (C) 1998, 2000, 2001 Free Software Foundation, Inc.
60371a2e
RS
4
5;; Author: Manuel Serrano <Manuel.Serrano@unice.fr>
1d8a80f0 6;; Keywords: convenience
60371a2e 7
e8af40ee 8;; This file is part of GNU Emacs.
60371a2e
RS
9
10;; GNU Emacs is free software; you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation; either version 2, or (at your option)
13;; any later version.
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs; see the file COPYING. If not, write to the
22;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23;; Boston, MA 02111-1307, USA.
24
3215afc4 25;;; Commentary:
60371a2e
RS
26;;
27;; Flyspell is a minor Emacs mode performing on-the-fly spelling
1d8a80f0 28;; checking.
3215afc4 29;;
1f44857f 30;; To enable Flyspell minor mode, type M-x flyspell-mode.
65a5c06a 31;; This applies only to the current buffer.
3215afc4
GM
32;;
33;; To enable Flyspell in text representing computer programs, type
1f44857f
DL
34;; M-x flyspell-prog-mode.
35;; In that mode only text inside comments is checked.
60371a2e 36;;
0a67052f 37;; Note: consider setting the variable ispell-parser to `tex' to
3215afc4 38;; avoid TeX command checking; use `(setq ispell-parser 'tex)'.
60371a2e
RS
39;;
40;; Some user variables control the behavior of flyspell. They are
41;; those defined under the `User variables' comment.
60371a2e
RS
42
43;;; Code:
60371a2e
RS
44(require 'ispell)
45
65a5c06a
RS
46;*---------------------------------------------------------------------*/
47;* Group ... */
48;*---------------------------------------------------------------------*/
60371a2e 49(defgroup flyspell nil
25f2ad05 50 "Spell checking on the fly."
60371a2e
RS
51 :tag "FlySpell"
52 :prefix "flyspell-"
3215afc4 53 :group 'processes)
60371a2e
RS
54
55;*---------------------------------------------------------------------*/
3215afc4 56;* User configuration ... */
60371a2e
RS
57;*---------------------------------------------------------------------*/
58(defcustom flyspell-highlight-flag t
0a67052f
RS
59 "*How Flyspell should indicate misspelled words.
60Non-nil means use highlight, nil means use minibuffer messages."
60371a2e
RS
61 :group 'flyspell
62 :type 'boolean)
63
0a67052f 64(defcustom flyspell-mark-duplications-flag t
2ed1f669 65 "*Non-nil means Flyspell reports a repeated word as an error."
60371a2e
RS
66 :group 'flyspell
67 :type 'boolean)
68
3215afc4 69(defcustom flyspell-sort-corrections nil
60371a2e
RS
70 "*Non-nil means, sort the corrections alphabetically before popping them."
71 :group 'flyspell
1f44857f 72 :version "21.1"
60371a2e
RS
73 :type 'boolean)
74
3215afc4 75(defcustom flyspell-duplicate-distance -1
2ed1f669
RS
76 "*The maximum distance for finding duplicates of unrecognized words.
77This applies to the feature that when a word is not found in the dictionary,
78if the same spelling occurs elsewhere in the buffer,
79Flyspell uses a different face (`flyspell-duplicate-face') to highlight it.
80This variable specifies how far to search to find such a duplicate.
65a5c06a 81-1 means no limit (search the whole buffer).
2ed1f669 820 means do not search for duplicate unrecognized spellings."
60371a2e 83 :group 'flyspell
1f44857f 84 :version "21.1"
60371a2e
RS
85 :type 'number)
86
87(defcustom flyspell-delay 3
65a5c06a 88 "*The number of seconds to wait before checking, after a \"delayed\" command."
60371a2e
RS
89 :group 'flyspell
90 :type 'number)
91
92(defcustom flyspell-persistent-highlight t
84770261
RS
93 "*Non-nil means misspelled words remain highlighted until corrected.
94If this variable is nil, only the most recently detected misspelled word
95is highlighted."
60371a2e
RS
96 :group 'flyspell
97 :type 'boolean)
98
99(defcustom flyspell-highlight-properties t
0a67052f 100 "*Non-nil means highlight incorrect words even if a property exists for this word."
60371a2e
RS
101 :group 'flyspell
102 :type 'boolean)
103
104(defcustom flyspell-default-delayed-commands
105 '(self-insert-command
106 delete-backward-char
3215afc4
GM
107 backward-or-forward-delete-char
108 delete-char
109 scrollbar-vertical-drag)
0a67052f
RS
110 "The standard list of delayed commands for Flyspell.
111See `flyspell-delayed-commands'."
60371a2e 112 :group 'flyspell
1f44857f 113 :version "21.1"
60371a2e
RS
114 :type '(repeat (symbol)))
115
0a67052f
RS
116(defcustom flyspell-delayed-commands nil
117 "List of commands that are \"delayed\" for Flyspell mode.
65a5c06a
RS
118After these commands, Flyspell checking is delayed for a short time,
119whose length is specified by `flyspell-delay'."
60371a2e
RS
120 :group 'flyspell
121 :type '(repeat (symbol)))
122
3215afc4
GM
123(defcustom flyspell-default-deplacement-commands
124 '(next-line
125 previous-line
126 scroll-up
127 scroll-down)
128 "The standard list of deplacement commands for Flyspell.
129See `flyspell-deplacement-commands'."
130 :group 'flyspell
1f44857f 131 :version "21.1"
3215afc4
GM
132 :type '(repeat (symbol)))
133
134(defcustom flyspell-deplacement-commands nil
135 "List of commands that are \"deplacement\" for Flyspell mode.
136After these commands, Flyspell checking is performed only if the previous
137command was not the very same command."
138 :group 'flyspell
1f44857f 139 :version "21.1"
3215afc4
GM
140 :type '(repeat (symbol)))
141
60371a2e 142(defcustom flyspell-issue-welcome-flag t
65a5c06a 143 "*Non-nil means that Flyspell should display a welcome message when started."
60371a2e
RS
144 :group 'flyspell
145 :type 'boolean)
146
3215afc4
GM
147(defcustom flyspell-incorrect-hook nil
148 "*List of functions to be called when incorrect words are encountered.
149Each function is given three arguments: the beginning and the end
1f44857f 150of the incorrect region. The third is either the symbol 'doublon' or the list
25f2ad05 151of possible corrections as returned by 'ispell-parse-output'.
3215afc4 152
25f2ad05 153If any of the functions return non-Nil, the word is not highlighted as
3215afc4
GM
154incorrect."
155 :group 'flyspell
1f44857f 156 :version "21.1"
3215afc4
GM
157 :type 'hook)
158
487301c2 159(defcustom flyspell-default-dictionary nil
3215afc4 160 "A string that is the name of the default dictionary.
1f44857f 161This is passed to the `ispell-change-dictionary' when flyspell is started.
487301c2
RS
162If the variable `ispell-local-dictionary' or `ispell-dictionary' is non-nil
163when flyspell is started, the value of that variable is used instead
164of `flyspell-default-dictionary' to select the default dictionary.
165Otherwise, if `flyspell-default-dictionary' is nil, it means to use
166Ispell's ultimate default dictionary."
3215afc4 167 :group 'flyspell
1f44857f 168 :version "21.1"
eb1d71ab 169 :type '(choice string (const :tag "Default" nil)))
3215afc4
GM
170
171(defcustom flyspell-tex-command-regexp
172 "\\(\\(begin\\|end\\)[ \t]*{\\|\\(cite[a-z*]*\\|label\\|ref\\|eqref\\|usepackage\\|documentclass\\)[ \t]*\\(\\[[^]]*\\]\\)?{[^{}]*\\)"
173 "A string that is the regular expression that matches TeX commands."
174 :group 'flyspell
1f44857f 175 :version "21.1"
3215afc4
GM
176 :type 'string)
177
178(defcustom flyspell-check-tex-math-command nil
25f2ad05 179 "*Non nil means check even inside TeX math environment.
1f44857f
DL
180TeX math environments are discovered by the TEXMATHP that implemented
181inside the texmathp.el Emacs package. That package may be found at:
3215afc4 182http://strw.leidenuniv.nl/~dominik/Tools"
60371a2e
RS
183 :group 'flyspell
184 :type 'boolean)
185
3215afc4
GM
186(defcustom flyspell-dictionaries-that-consider-dash-as-word-delimiter
187 '("francais" "deutsch8" "norsk")
188 "List of dictionary names that consider `-' as word delimiter."
189 :group 'flyspell
1f44857f 190 :version "21.1"
3215afc4 191 :type '(repeat (string)))
60371a2e 192
3215afc4
GM
193(defcustom flyspell-abbrev-p
194 t
195 "*If true, add correction to abbreviation table."
60371a2e 196 :group 'flyspell
1f44857f 197 :version "21.1"
60371a2e
RS
198 :type 'boolean)
199
3215afc4
GM
200(defcustom flyspell-use-global-abbrev-table-p
201 nil
202 "*If true, prefer global abbrev table to local abbrev table."
203 :group 'flyspell
1f44857f 204 :version "21.1"
3215afc4
GM
205 :type 'boolean)
206
207;;;###autoload
208(defcustom flyspell-mode-line-string " Fly"
209 "*String displayed on the modeline when flyspell is active.
210Set this to nil if you don't want a modeline indicator."
211 :group 'flyspell
212 :type 'string)
213
214(defcustom flyspell-large-region 1000
1f44857f
DL
215 "*The threshold that determines if a region is small.
216The `flyspell-region' function is invoked if the region is small, the
217word are checked one after the other using regular flyspell check
218means. If the region is large, a new Ispell process is spawned to get
219speed."
3215afc4 220 :group 'flyspell
1f44857f 221 :version "21.1"
3215afc4
GM
222 :type 'number)
223
60371a2e
RS
224;*---------------------------------------------------------------------*/
225;* Mode specific options */
226;* ------------------------------------------------------------- */
227;* Mode specific options enable users to disable flyspell on */
228;* certain word depending of the emacs mode. For instance, when */
229;* using flyspell with mail-mode add the following expression */
230;* in your .emacs file: */
231;* (add-hook 'mail-mode */
232;* '(lambda () (setq flyspell-generic-check-word-p */
233;* 'mail-mode-flyspell-verify))) */
234;*---------------------------------------------------------------------*/
235(defvar flyspell-generic-check-word-p nil
236 "Function providing per-mode customization over which words are flyspelled.
0a67052f
RS
237Returns t to continue checking, nil otherwise.
238Flyspell mode sets this variable to whatever is the `flyspell-mode-predicate'
239property of the major mode name.")
60371a2e
RS
240(make-variable-buffer-local 'flyspell-generic-check-word-p)
241
3215afc4 242;*--- mail mode -------------------------------------------------------*/
0a67052f
RS
243(put 'mail-mode 'flyspell-mode-predicate 'mail-mode-flyspell-verify)
244(put 'message-mode 'flyspell-mode-predicate 'mail-mode-flyspell-verify)
60371a2e 245(defun mail-mode-flyspell-verify ()
0a67052f 246 "This function is used for `flyspell-generic-check-word-p' in Mail mode."
53287eb0
GM
247 (let ((in-headers (save-excursion
248 (re-search-forward mail-header-separator nil t)))
249 (in-signature (save-excursion
250 (re-search-backward message-signature-separator nil t))))
251 (cond (in-headers
25c99c6d
GM
252 (and (save-excursion (beginning-of-line)
253 (looking-at "^Subject:"))
254 (> (point) (match-end 0))))
53287eb0
GM
255 (in-signature
256 nil)
257 (t
258 (save-excursion
259 (beginning-of-line)
260 (not (looking-at "[>}|]\\To:")))))))
60371a2e 261
3215afc4 262;*--- texinfo mode ----------------------------------------------------*/
0a67052f 263(put 'texinfo-mode 'flyspell-mode-predicate 'texinfo-mode-flyspell-verify)
60371a2e 264(defun texinfo-mode-flyspell-verify ()
0a67052f 265 "This function is used for `flyspell-generic-check-word-p' in Texinfo mode."
60371a2e
RS
266 (save-excursion
267 (forward-word -1)
268 (not (looking-at "@"))))
269
3215afc4
GM
270;*--- tex mode --------------------------------------------------------*/
271(put 'tex-mode 'flyspell-mode-predicate 'tex-mode-flyspell-verify)
272(defun tex-mode-flyspell-verify ()
273 "This function is used for `flyspell-generic-check-word-p' in LaTeX mode."
274 (and
275 (not (save-excursion
276 (re-search-backward "^[ \t]*%%%[ \t]+Local" (point-min) t)))
277 (not (save-excursion
278 (let ((this (point-marker))
279 (e (progn (end-of-line) (point-marker))))
280 (beginning-of-line)
281 (if (re-search-forward "\\\\\\(cite\\|label\\|ref\\){[^}]*}" e t)
282 (and (>= this (match-beginning 0))
283 (<= this (match-end 0)) )))))))
284
285;*--- sgml mode -------------------------------------------------------*/
286(put 'sgml-mode 'flyspell-mode-predicate 'sgml-mode-flyspell-verify)
287(put 'html-mode 'flyspell-mode-predicate 'sgml-mode-flyspell-verify)
288
289(defun sgml-mode-flyspell-verify ()
290 "This function is used for `flyspell-generic-check-word-p' in SGML mode."
291 (not (save-excursion
292 (let ((this (point-marker))
293 (s (progn (beginning-of-line) (point-marker)))
294 (e (progn (end-of-line) (point-marker))))
295 (or (progn
296 (goto-char this)
297 (and (re-search-forward "[^<]*>" e t)
298 (= (match-beginning 0) this)))
299 (progn
300 (goto-char this)
301 (and (re-search-backward "<[^>]*" s t)
302 (= (match-end 0) this)))
303 (and (progn
304 (goto-char this)
305 (and (re-search-forward "[^&]*;" e t)
306 (= (match-beginning 0) this)))
307 (progn
308 (goto-char this)
309 (and (re-search-backward "&[^;]*" s t)
310 (= (match-end 0) this)))))))))
311
312;*---------------------------------------------------------------------*/
313;* Programming mode */
314;*---------------------------------------------------------------------*/
9b047c69
SM
315(defvar flyspell-prog-text-faces
316 '(font-lock-string-face font-lock-comment-face font-lock-doc-face)
317 "Faces corresponding to text in programming-mode buffers.")
318
3215afc4
GM
319(defun flyspell-generic-progmode-verify ()
320 "Used for `flyspell-generic-check-word-p' in programming modes."
321 (let ((f (get-text-property (point) 'face)))
9b047c69 322 (memq f flyspell-prog-text-faces)))
3215afc4
GM
323
324;;;###autoload
325(defun flyspell-prog-mode ()
326 "Turn on `flyspell-mode' for comments and strings."
327 (interactive)
328 (setq flyspell-generic-check-word-p 'flyspell-generic-progmode-verify)
329 (flyspell-mode 1))
330
60371a2e
RS
331;*---------------------------------------------------------------------*/
332;* Overlay compatibility */
333;*---------------------------------------------------------------------*/
3215afc4
GM
334(autoload 'make-overlay "overlay" "Overlay compatibility kit." t)
335(autoload 'overlayp "overlay" "Overlay compatibility kit." t)
336(autoload 'overlays-in "overlay" "Overlay compatibility kit." t)
337(autoload 'delete-overlay "overlay" "Overlay compatibility kit." t)
338(autoload 'overlays-at "overlay" "Overlay compatibility kit." t)
339(autoload 'overlay-put "overlay" "Overlay compatibility kit." t)
340(autoload 'overlay-get "overlay" "Overlay compatibility kit." t)
341(autoload 'previous-overlay-change "overlay" "Overlay compatibility kit." t)
60371a2e 342
60371a2e
RS
343;*---------------------------------------------------------------------*/
344;* Which emacs are we currently running */
345;*---------------------------------------------------------------------*/
346(defvar flyspell-emacs
347 (cond
348 ((string-match "XEmacs" emacs-version)
349 'xemacs)
350 (t
351 'emacs))
65a5c06a 352 "The type of Emacs we are currently running.")
60371a2e 353
dd0ffc28
RS
354(defvar flyspell-use-local-map
355 (or (eq flyspell-emacs 'xemacs)
356 (not (string< emacs-version "20"))))
357
60371a2e
RS
358;*---------------------------------------------------------------------*/
359;* The minor mode declaration. */
360;*---------------------------------------------------------------------*/
361(defvar flyspell-mode nil)
362(make-variable-buffer-local 'flyspell-mode)
363
2be80b63
DL
364(defvar flyspell-mouse-map
365 (let ((map (make-sparse-keymap)))
366 (cond
367 ((eq flyspell-emacs 'xemacs)
a63f25a4 368 (define-key map [(button2)] #'flyspell-correct-word)
7465ebef 369 (define-key map "\M-\t" #'flyspell-auto-correct-word))
2be80b63 370 (flyspell-use-local-map
a63f25a4 371 (define-key map [(mouse-2)] #'flyspell-correct-word)
2be80b63
DL
372 (define-key map "\M-\t" #'flyspell-auto-correct-word)))
373 map))
60371a2e 374
60371a2e 375;; the name of the overlay property that defines the keymap
0dd10e62 376(defvar flyspell-overlay-keymap-property-name 'keymap)
3215afc4
GM
377
378;; dash character machinery
379(defvar flyspell-consider-dash-as-word-delimiter-flag nil
380 "*Non-nil means that the `-' char is considered as a word delimiter.")
381(make-variable-buffer-local 'flyspell-consider-dash-as-word-delimiter-flag)
382(defvar flyspell-dash-dictionary nil)
383(make-variable-buffer-local 'flyspell-dash-dictionary)
384(defvar flyspell-dash-local-dictionary nil)
385(make-variable-buffer-local 'flyspell-dash-local-dictionary)
386
60371a2e
RS
387;*---------------------------------------------------------------------*/
388;* Highlighting */
389;*---------------------------------------------------------------------*/
0a67052f 390(defface flyspell-incorrect-face
65a5c06a 391 '((((class color)) (:foreground "OrangeRed" :bold t :underline t))
0a67052f 392 (t (:bold t)))
2ed1f669 393 "Face used for marking a misspelled word in Flyspell."
0a67052f
RS
394 :group 'flyspell)
395
396(defface flyspell-duplicate-face
65a5c06a 397 '((((class color)) (:foreground "Gold3" :bold t :underline t))
0a67052f 398 (t (:bold t)))
2ed1f669
RS
399 "Face used for marking a misspelled word that appears twice in the buffer.
400See also `flyspell-duplicate-distance'."
0a67052f
RS
401 :group 'flyspell)
402
60371a2e
RS
403(defvar flyspell-overlay nil)
404
405;*---------------------------------------------------------------------*/
406;* flyspell-mode ... */
407;*---------------------------------------------------------------------*/
408;;;###autoload
409(defun flyspell-mode (&optional arg)
410 "Minor mode performing on-the-fly spelling checking.
411Ispell is automatically spawned on background for each entered words.
0a67052f
RS
412The default flyspell behavior is to highlight incorrect words.
413With no argument, this command toggles Flyspell mode.
414With a prefix argument ARG, turn Flyspell minor mode on iff ARG is positive.
60371a2e
RS
415
416Bindings:
417\\[ispell-word]: correct words (using Ispell).
418\\[flyspell-auto-correct-word]: automatically correct word.
419\\[flyspell-correct-word] (or mouse-2): popup correct words.
420
421Hooks:
0dd10e62 422This runs `flyspell-mode-hook' after flyspell is entered.
60371a2e
RS
423
424Remark:
425`flyspell-mode' uses `ispell-mode'. Thus all Ispell options are
426valid. For instance, a personal dictionary can be used by
427invoking `ispell-change-dictionary'.
428
429Consider using the `ispell-parser' to check your text. For instance
430consider adding:
0a67052f 431\(add-hook 'tex-mode-hook (function (lambda () (setq ispell-parser 'tex))))
60371a2e
RS
432in your .emacs file.
433
0dd10e62
RS
434\\[flyspell-region] checks all words inside a region.
435\\[flyspell-buffer] checks the whole buffer."
60371a2e 436 (interactive "P")
ca2ebe63
KH
437 (let ((old-flyspell-mode flyspell-mode))
438 ;; Mark the mode as on or off.
439 (setq flyspell-mode (not (or (and (null arg) flyspell-mode)
440 (<= (prefix-numeric-value arg) 0))))
441 ;; Do the real work.
442 (unless (eq flyspell-mode old-flyspell-mode)
443 (if flyspell-mode
444 (flyspell-mode-on)
445 (flyspell-mode-off))
446 ;; Force modeline redisplay.
447 (set-buffer-modified-p (buffer-modified-p)))))
60371a2e 448
3215afc4
GM
449;*---------------------------------------------------------------------*/
450;* Autoloading */
451;*---------------------------------------------------------------------*/
1f44857f 452;;;###autoload
0dd10e62
RS
453(add-minor-mode 'flyspell-mode
454 'flyspell-mode-line-string
455 nil
456 nil
457 'flyspell-mode)
3215afc4
GM
458
459;*---------------------------------------------------------------------*/
460;* flyspell-buffers ... */
461;* ------------------------------------------------------------- */
462;* For remembering buffers running flyspell */
463;*---------------------------------------------------------------------*/
464(defvar flyspell-buffers nil)
465
466;*---------------------------------------------------------------------*/
467;* flyspell-minibuffer-p ... */
468;*---------------------------------------------------------------------*/
469(defun flyspell-minibuffer-p (buffer)
470 "Is BUFFER a minibuffer?"
471 (let ((ws (get-buffer-window-list buffer t)))
472 (and (consp ws) (window-minibuffer-p (car ws)))))
473
474;*---------------------------------------------------------------------*/
475;* flyspell-accept-buffer-local-defs ... */
476;*---------------------------------------------------------------------*/
477(defun flyspell-accept-buffer-local-defs ()
478 (ispell-accept-buffer-local-defs)
479 (if (not (and (eq flyspell-dash-dictionary ispell-dictionary)
480 (eq flyspell-dash-local-dictionary ispell-local-dictionary)))
481 ;; the dictionary as changed
482 (progn
483 (setq flyspell-dash-dictionary ispell-dictionary)
484 (setq flyspell-dash-local-dictionary ispell-local-dictionary)
485 (if (member (or ispell-local-dictionary ispell-dictionary)
486 flyspell-dictionaries-that-consider-dash-as-word-delimiter)
487 (setq flyspell-consider-dash-as-word-delimiter-flag t)
488 (setq flyspell-consider-dash-as-word-delimiter-flag nil)))))
489
60371a2e
RS
490;*---------------------------------------------------------------------*/
491;* flyspell-mode-on ... */
492;*---------------------------------------------------------------------*/
2be80b63
DL
493(eval-when-compile (defvar flyspell-local-mouse-map))
494
60371a2e 495(defun flyspell-mode-on ()
59e7a637 496 "Turn Flyspell mode on. Do not use this; use `flyspell-mode' instead."
60371a2e 497 (setq ispell-highlight-face 'flyspell-incorrect-face)
3215afc4
GM
498 ;; local dictionaries setup
499 (ispell-change-dictionary
500 (or ispell-local-dictionary ispell-dictionary flyspell-default-dictionary))
501 ;; we have to force ispell to accept the local definition or
502 ;; otherwise it could be too late, the local dictionary may
503 ;; be forgotten!
504 (flyspell-accept-buffer-local-defs)
505 ;; we put the `flyspel-delayed' property on some commands
60371a2e 506 (flyspell-delay-commands)
3215afc4
GM
507 ;; we put the `flyspel-deplacement' property on some commands
508 (flyspell-deplacement-commands)
60371a2e 509 ;; we bound flyspell action to post-command hook
2ed1f669 510 (add-hook 'post-command-hook (function flyspell-post-command-hook) t t)
60371a2e 511 ;; we bound flyspell action to pre-command hook
2ed1f669 512 (add-hook 'pre-command-hook (function flyspell-pre-command-hook) t t)
3215afc4
GM
513 ;; we bound flyspell action to after-change hook
514 (make-local-variable 'after-change-functions)
515 (setq after-change-functions
516 (cons 'flyspell-after-change-function after-change-functions))
517 ;; set flyspell-generic-check-word-p based on the major mode
0a67052f
RS
518 (let ((mode-predicate (get major-mode 'flyspell-mode-predicate)))
519 (if mode-predicate
520 (setq flyspell-generic-check-word-p mode-predicate)))
60371a2e 521 ;; the welcome message
f5ed37df 522 (if (and flyspell-issue-welcome-flag (interactive-p))
65a5c06a
RS
523 (let ((binding (where-is-internal 'flyspell-auto-correct-word
524 nil 'non-ascii)))
525 (message
526 (if binding
3215afc4 527 (format "Welcome to flyspell. Use %s or Mouse-2 to correct words."
65a5c06a 528 (key-description binding))
3215afc4 529 "Welcome to flyspell. Use Mouse-2 to correct words."))))
3215afc4 530
60371a2e
RS
531 ;; we end with the flyspell hooks
532 (run-hooks 'flyspell-mode-hook))
533
534;*---------------------------------------------------------------------*/
535;* flyspell-delay-commands ... */
536;*---------------------------------------------------------------------*/
537(defun flyspell-delay-commands ()
59e7a637 538 "Install the standard set of Flyspell delayed commands."
60371a2e
RS
539 (mapcar 'flyspell-delay-command flyspell-default-delayed-commands)
540 (mapcar 'flyspell-delay-command flyspell-delayed-commands))
541
542;*---------------------------------------------------------------------*/
543;* flyspell-delay-command ... */
544;*---------------------------------------------------------------------*/
545(defun flyspell-delay-command (command)
59e7a637 546 "Set COMMAND to be delayed, for Flyspell.
60371a2e 547When flyspell `post-command-hook' is invoked because a delayed command
25f2ad05 548as been used the current word is not immediately checked.
0a67052f
RS
549It will be checked only after `flyspell-delay' seconds."
550 (interactive "SDelay Flyspell after Command: ")
60371a2e
RS
551 (put command 'flyspell-delayed t))
552
553;*---------------------------------------------------------------------*/
3215afc4 554;* flyspell-deplacement-commands ... */
60371a2e 555;*---------------------------------------------------------------------*/
3215afc4
GM
556(defun flyspell-deplacement-commands ()
557 "Install the standard set of Flyspell deplacement commands."
558 (mapcar 'flyspell-deplacement-command flyspell-default-deplacement-commands)
559 (mapcar 'flyspell-deplacement-command flyspell-deplacement-commands))
60371a2e
RS
560
561;*---------------------------------------------------------------------*/
3215afc4 562;* flyspell-deplacement-command ... */
60371a2e 563;*---------------------------------------------------------------------*/
3215afc4
GM
564(defun flyspell-deplacement-command (command)
565 "Set COMMAND that implement cursor movements, for Flyspell.
566When flyspell `post-command-hook' is invoked because of a deplacement command
567as been used the current word is checked only if the previous command was
568not the very same deplacement command."
569 (interactive "SDeplacement Flyspell after Command: ")
570 (put command 'flyspell-deplacement t))
60371a2e
RS
571
572;*---------------------------------------------------------------------*/
573;* flyspell-word-cache ... */
574;*---------------------------------------------------------------------*/
575(defvar flyspell-word-cache-start nil)
576(defvar flyspell-word-cache-end nil)
577(defvar flyspell-word-cache-word nil)
578(make-variable-buffer-local 'flyspell-word-cache-start)
579(make-variable-buffer-local 'flyspell-word-cache-end)
580(make-variable-buffer-local 'flyspell-word-cache-word)
581
582;*---------------------------------------------------------------------*/
583;* The flyspell pre-hook, store the current position. In the */
584;* post command hook, we will check, if the word at this position */
585;* has to be spell checked. */
586;*---------------------------------------------------------------------*/
3215afc4
GM
587(defvar flyspell-pre-buffer nil)
588(defvar flyspell-pre-point nil)
589(defvar flyspell-pre-column nil)
590(defvar flyspell-pre-pre-buffer nil)
591(defvar flyspell-pre-pre-point nil)
592
593;*---------------------------------------------------------------------*/
594;* flyspell-previous-command ... */
595;*---------------------------------------------------------------------*/
596(defvar flyspell-previous-command nil
597 "The last interactive command checked by Flyspell.")
60371a2e
RS
598
599;*---------------------------------------------------------------------*/
600;* flyspell-pre-command-hook ... */
601;*---------------------------------------------------------------------*/
602(defun flyspell-pre-command-hook ()
0a67052f 603 "Save the current buffer and point for Flyspell's post-command hook."
60371a2e
RS
604 (interactive)
605 (setq flyspell-pre-buffer (current-buffer))
3215afc4
GM
606 (setq flyspell-pre-point (point))
607 (setq flyspell-pre-column (current-column)))
60371a2e
RS
608
609;*---------------------------------------------------------------------*/
610;* flyspell-mode-off ... */
611;*---------------------------------------------------------------------*/
59e7a637 612;;;###autoload
60371a2e 613(defun flyspell-mode-off ()
59e7a637 614 "Turn Flyspell mode off."
60371a2e 615 ;; we remove the hooks
2ed1f669
RS
616 (remove-hook 'post-command-hook (function flyspell-post-command-hook) t)
617 (remove-hook 'pre-command-hook (function flyspell-pre-command-hook) t)
3215afc4
GM
618 (setq after-change-functions (delq 'flyspell-after-change-function
619 after-change-functions))
60371a2e
RS
620 ;; we remove all the flyspell hilightings
621 (flyspell-delete-all-overlays)
622 ;; we have to erase pre cache variables
623 (setq flyspell-pre-buffer nil)
624 (setq flyspell-pre-point nil)
625 ;; we mark the mode as killed
626 (setq flyspell-mode nil))
627
60371a2e
RS
628;*---------------------------------------------------------------------*/
629;* flyspell-check-pre-word-p ... */
630;*---------------------------------------------------------------------*/
631(defun flyspell-check-pre-word-p ()
0a67052f
RS
632 "Return non-nil if we should to check the word before point.
633More precisely, it applies to the word that was before point
634before the current command."
60371a2e
RS
635 (cond
636 ((or (not (numberp flyspell-pre-point))
637 (not (bufferp flyspell-pre-buffer))
638 (not (buffer-live-p flyspell-pre-buffer)))
639 nil)
3215afc4
GM
640 ((and (eq flyspell-pre-pre-point flyspell-pre-point)
641 (eq flyspell-pre-pre-buffer flyspell-pre-buffer))
642 nil)
65a5c06a
RS
643 ((or (and (= flyspell-pre-point (- (point) 1))
644 (eq (char-syntax (char-after flyspell-pre-point)) ?w))
60371a2e
RS
645 (= flyspell-pre-point (point))
646 (= flyspell-pre-point (+ (point) 1)))
647 nil)
3215afc4
GM
648 ((and (symbolp this-command)
649 (or (get this-command 'flyspell-delayed)
650 (and (get this-command 'flyspell-deplacement)
651 (eq flyspell-previous-command this-command)))
652 (or (= (current-column) 0)
653 (= (current-column) flyspell-pre-column)
654 (eq (char-syntax (char-after flyspell-pre-point)) ?w)))
655 nil)
60371a2e
RS
656 ((not (eq (current-buffer) flyspell-pre-buffer))
657 t)
658 ((not (and (numberp flyspell-word-cache-start)
659 (numberp flyspell-word-cache-end)))
660 t)
661 (t
662 (or (< flyspell-pre-point flyspell-word-cache-start)
663 (> flyspell-pre-point flyspell-word-cache-end)))))
3215afc4
GM
664
665;*---------------------------------------------------------------------*/
666;* The flyspell after-change-hook, store the change position. In */
667;* the post command hook, we will check, if the word at this */
668;* position has to be spell checked. */
669;*---------------------------------------------------------------------*/
670(defvar flyspell-changes nil)
671
672;*---------------------------------------------------------------------*/
673;* flyspell-after-change-function ... */
674;*---------------------------------------------------------------------*/
675(defun flyspell-after-change-function (start stop len)
676 "Save the current buffer and point for Flyspell's post-command hook."
677 (interactive)
678 (setq flyspell-changes (cons (cons start stop) flyspell-changes)))
679
680;*---------------------------------------------------------------------*/
681;* flyspell-check-changed-word-p ... */
682;*---------------------------------------------------------------------*/
683(defun flyspell-check-changed-word-p (start stop)
684 "Return t when the changed word has to be checked.
685The answer depends of several criteria.
686Mostly we check word delimiters."
687 (cond
688 ((and (eq (char-after start) ?\n) (> stop start))
689 t)
690 ((not (numberp flyspell-pre-point))
691 t)
692 ((and (>= flyspell-pre-point start) (<= flyspell-pre-point stop))
693 nil)
694 ((let ((pos (point)))
695 (or (>= pos start) (<= pos stop) (= pos (1+ stop))))
696 nil)
697 (t
698 t)))
699
700;*---------------------------------------------------------------------*/
701;* flyspell-check-word-p ... */
702;*---------------------------------------------------------------------*/
703(defun flyspell-check-word-p ()
704 "Return t when the word at `point' has to be checked.
705The answer depends of several criteria.
706Mostly we check word delimiters."
707 (cond
708 ((<= (- (point-max) 1) (point-min))
709 ;; the buffer is not filled enough
710 nil)
711 ((and (and (> (current-column) 0)
712 (not (eq (current-column) flyspell-pre-column)))
713 (save-excursion
714 (backward-char 1)
715 (and (looking-at (flyspell-get-not-casechars))
716 (or flyspell-consider-dash-as-word-delimiter-flag
717 (not (looking-at "\\-"))))))
718 ;; yes because we have reached or typed a word delimiter.
719 t)
720 ((symbolp this-command)
721 (cond
722 ((get this-command 'flyspell-deplacement)
723 (not (eq flyspell-previous-command this-command)))
724 ((get this-command 'flyspell-delayed)
725 ;; the current command is not delayed, that
726 ;; is that we must check the word now
727 (if (fboundp 'about-xemacs)
728 (sit-for flyspell-delay nil)
729 (sit-for flyspell-delay 0 nil)))
730 (t t)))
731 (t t)))
732
733;*---------------------------------------------------------------------*/
734;* flyspell-debug-signal-no-check ... */
735;*---------------------------------------------------------------------*/
736(defun flyspell-debug-signal-no-check (msg obj)
737 (setq debug-on-error t)
738 (save-excursion
739 (let ((buffer (get-buffer-create "*flyspell-debug*")))
740 (set-buffer buffer)
741 (erase-buffer)
742 (insert "NO-CHECK:\n")
743 (insert (format " %S : %S\n" msg obj)))))
744
745;*---------------------------------------------------------------------*/
1f44857f 746;* flyspell-debug-signal-pre-word-checked ... */
3215afc4
GM
747;*---------------------------------------------------------------------*/
748(defun flyspell-debug-signal-pre-word-checked ()
749 (setq debug-on-error t)
750 (save-excursion
751 (let ((buffer (get-buffer-create "*flyspell-debug*")))
752 (set-buffer buffer)
753 (insert "PRE-WORD:\n")
754 (insert (format " pre-point : %S\n" flyspell-pre-point))
755 (insert (format " pre-buffer : %S\n" flyspell-pre-buffer))
756 (insert (format " cache-start: %S\n" flyspell-word-cache-start))
757 (insert (format " cache-end : %S\n" flyspell-word-cache-end))
758 (goto-char (point-max)))))
759
760;*---------------------------------------------------------------------*/
1f44857f 761;* flyspell-debug-signal-word-checked ... */
3215afc4
GM
762;*---------------------------------------------------------------------*/
763(defun flyspell-debug-signal-word-checked ()
764 (setq debug-on-error t)
765 (save-excursion
766 (let ((oldbuf (current-buffer))
767 (buffer (get-buffer-create "*flyspell-debug*"))
768 (point (point)))
769 (set-buffer buffer)
770 (insert "WORD:\n")
771 (insert (format " this-cmd : %S\n" this-command))
772 (insert (format " delayed : %S\n" (and (symbolp this-command)
773 (get this-command 'flyspell-delayed))))
774 (insert (format " point : %S\n" point))
775 (insert (format " prev-char : [%c] %S\n"
776 (progn
777 (set-buffer oldbuf)
778 (let ((c (if (> (point) (point-min))
779 (save-excursion
780 (backward-char 1)
781 (char-after (point)))
782 ? )))
783 (set-buffer buffer)
784 c))
785 (progn
786 (set-buffer oldbuf)
787 (let ((c (if (> (point) (point-min))
788 (save-excursion
789 (backward-char 1)
790 (and (and (looking-at (flyspell-get-not-casechars)) 1)
791 (and (or flyspell-consider-dash-as-word-delimiter-flag
792 (not (looking-at "\\-"))) 2))))))
793 (set-buffer buffer)
794 c))))
795 (insert (format " because : %S\n"
796 (cond
797 ((not (and (symbolp this-command)
798 (get this-command 'flyspell-delayed)))
799 ;; the current command is not delayed, that
800 ;; is that we must check the word now
801 'not-delayed)
802 ((progn
803 (set-buffer oldbuf)
804 (let ((c (if (> (point) (point-min))
805 (save-excursion
806 (backward-char 1)
807 (and (looking-at (flyspell-get-not-casechars))
808 (or flyspell-consider-dash-as-word-delimiter-flag
809 (not (looking-at "\\-"))))))))
810 (set-buffer buffer)
811 c))
812 ;; yes because we have reached or typed a word delimiter.
813 'separator)
814 ((not (integerp flyspell-delay))
815 ;; yes because the user had set up a no-delay configuration.
816 'no-delay)
817 (t
818 'sit-for))))
819 (goto-char (point-max)))))
820
821;*---------------------------------------------------------------------*/
1f44857f 822;* flyspell-debug-signal-changed-checked ... */
3215afc4
GM
823;*---------------------------------------------------------------------*/
824(defun flyspell-debug-signal-changed-checked ()
825 (setq debug-on-error t)
826 (save-excursion
827 (let ((buffer (get-buffer-create "*flyspell-debug*"))
828 (point (point)))
829 (set-buffer buffer)
830 (insert "CHANGED WORD:\n")
831 (insert (format " point : %S\n" point))
832 (goto-char (point-max)))))
833
60371a2e
RS
834;*---------------------------------------------------------------------*/
835;* flyspell-post-command-hook ... */
3215afc4
GM
836;* ------------------------------------------------------------- */
837;* It is possible that we check several words: */
838;* 1- the current word is checked if the predicate */
839;* FLYSPELL-CHECK-WORD-P is true */
840;* 2- the word that used to be the current word before the */
841;* THIS-COMMAND is checked if: */
842;* a- the previous word is different from the current word */
843;* b- the previous word as not just been checked by the */
844;* previous FLYSPELL-POST-COMMAND-HOOK */
845;* 3- the words changed by the THIS-COMMAND that are neither the */
846;* previous word nor the current word */
60371a2e
RS
847;*---------------------------------------------------------------------*/
848(defun flyspell-post-command-hook ()
849 "The `post-command-hook' used by flyspell to check a word in-the-fly."
850 (interactive)
3215afc4
GM
851 (let ((command this-command))
852 (if (flyspell-check-pre-word-p)
60371a2e 853 (save-excursion
3215afc4
GM
854 '(flyspell-debug-signal-pre-word-checked)
855 (set-buffer flyspell-pre-buffer)
856 (save-excursion
857 (goto-char flyspell-pre-point)
858 (flyspell-word))))
859 (if (flyspell-check-word-p)
860 (progn
861 '(flyspell-debug-signal-word-checked)
862 (flyspell-word)
863 ;; we remember which word we have just checked.
864 ;; this will be used next time we will check a word
865 ;; to compare the next current word with the word
866 ;; that as been registered in the pre-command-hook
867 ;; that is these variables are used within the predicate
868 ;; FLYSPELL-CHECK-PRE-WORD-P
869 (setq flyspell-pre-pre-buffer (current-buffer))
870 (setq flyspell-pre-pre-point (point)))
871 (progn
872 (setq flyspell-pre-pre-buffer nil)
873 (setq flyspell-pre-pre-point nil)
874 ;; when a word is not checked because of a delayed command
875 ;; we do not disable the ispell cache.
876 (if (and (symbolp this-command) (get this-command 'flyspell-delayed))
877 (setq flyspell-word-cache-end -1))))
878 (while (consp flyspell-changes)
879 (let ((start (car (car flyspell-changes)))
880 (stop (cdr (car flyspell-changes))))
881 (if (flyspell-check-changed-word-p start stop)
882 (save-excursion
883 '(flyspell-debug-signal-changed-checked)
884 (goto-char start)
885 (flyspell-word)))
886 (setq flyspell-changes (cdr flyspell-changes))))
887 (setq flyspell-previous-command command)))
888
889;*---------------------------------------------------------------------*/
890;* flyspell-notify-misspell ... */
891;*---------------------------------------------------------------------*/
892(defun flyspell-notify-misspell (start end word poss)
893 (let ((replacements (if (stringp poss)
894 poss
895 (if flyspell-sort-corrections
896 (sort (car (cdr (cdr poss))) 'string<)
897 (car (cdr (cdr poss)))))))
898 (message (format "mispelling `%s' %S" word replacements))))
60371a2e
RS
899
900;*---------------------------------------------------------------------*/
901;* flyspell-word ... */
902;*---------------------------------------------------------------------*/
903(defun flyspell-word (&optional following)
904 "Spell check a word."
905 (interactive (list current-prefix-arg))
906 (if (interactive-p)
907 (setq following ispell-following-word))
908 (save-excursion
3215afc4 909 ;; use the correct dictionary
1f44857f 910 (flyspell-accept-buffer-local-defs)
3215afc4
GM
911 (let* ((cursor-location (point))
912 (flyspell-word (flyspell-get-word following))
913 start end poss word)
914 (if (or (eq flyspell-word nil)
60371a2e
RS
915 (and (fboundp flyspell-generic-check-word-p)
916 (not (funcall flyspell-generic-check-word-p))))
3215afc4 917 '()
60371a2e 918 (progn
3215afc4
GM
919 ;; destructure return flyspell-word info list.
920 (setq start (car (cdr flyspell-word))
921 end (car (cdr (cdr flyspell-word)))
922 word (car flyspell-word))
60371a2e
RS
923 ;; before checking in the directory, we check for doublons.
924 (cond
3215afc4
GM
925 ((and (or (not (eq ispell-parser 'tex))
926 (not (eq (char-after start) ?\\)))
927 flyspell-mark-duplications-flag
60371a2e
RS
928 (save-excursion
929 (goto-char start)
930 (word-search-backward word
931 (- start
932 (+ 1 (- end start)))
933 t)))
934 ;; yes, this is a doublon
3215afc4 935 (flyspell-highlight-incorrect-region start end 'doublon))
60371a2e
RS
936 ((and (eq flyspell-word-cache-start start)
937 (eq flyspell-word-cache-end end)
938 (string-equal flyspell-word-cache-word word))
939 ;; this word had been already checked, we skip
940 nil)
941 ((and (eq ispell-parser 'tex)
3215afc4 942 (flyspell-tex-command-p flyspell-word))
60371a2e
RS
943 ;; this is a correct word (because a tex command)
944 (flyspell-unhighlight-at start)
945 (if (> end start)
946 (flyspell-unhighlight-at (- end 1)))
947 t)
948 (t
949 ;; we setup the cache
950 (setq flyspell-word-cache-start start)
951 (setq flyspell-word-cache-end end)
952 (setq flyspell-word-cache-word word)
953 ;; now check spelling of word.
954 (process-send-string ispell-process "%\n")
955 ;; put in verbose mode
956 (process-send-string ispell-process
957 (concat "^" word "\n"))
65a5c06a
RS
958 ;; we mark the ispell process so it can be killed
959 ;; when emacs is exited without query
960 (if (fboundp 'process-kill-without-query)
961 (process-kill-without-query ispell-process))
60371a2e
RS
962 ;; wait until ispell has processed word
963 (while (progn
964 (accept-process-output ispell-process)
965 (not (string= "" (car ispell-filter)))))
966 ;; (process-send-string ispell-process "!\n")
967 ;; back to terse mode.
968 (setq ispell-filter (cdr ispell-filter))
3215afc4 969 (if (consp ispell-filter)
60371a2e
RS
970 (setq poss (ispell-parse-output (car ispell-filter))))
971 (cond ((eq poss t)
972 ;; correct
973 (flyspell-unhighlight-at start)
974 (if (> end start)
975 (flyspell-unhighlight-at (- end 1)))
976 t)
977 ((and (stringp poss) flyspell-highlight-flag)
978 ;; correct
979 (flyspell-unhighlight-at start)
980 (if (> end start)
981 (flyspell-unhighlight-at (- end 1)))
982 t)
983 ((null poss)
984 (flyspell-unhighlight-at start)
985 (if (> end start)
3215afc4 986 (flyspell-unhighlight-at (- end 1))))
60371a2e
RS
987 ((or (and (< flyspell-duplicate-distance 0)
988 (or (save-excursion
989 (goto-char start)
990 (word-search-backward word
991 (point-min)
992 t))
993 (save-excursion
994 (goto-char end)
995 (word-search-forward word
996 (point-max)
997 t))))
998 (and (> flyspell-duplicate-distance 0)
999 (or (save-excursion
1000 (goto-char start)
1001 (word-search-backward
1002 word
1003 (- start
1004 flyspell-duplicate-distance)
1005 t))
1006 (save-excursion
1007 (goto-char end)
1008 (word-search-forward
1009 word
1010 (+ end
1011 flyspell-duplicate-distance)
1012 t)))))
1013 (if flyspell-highlight-flag
1014 (flyspell-highlight-duplicate-region start end)
65a5c06a 1015 (message (format "duplicate `%s'" word))))
60371a2e
RS
1016 (t
1017 ;; incorrect highlight the location
1018 (if flyspell-highlight-flag
3215afc4
GM
1019 (flyspell-highlight-incorrect-region start end poss)
1020 (flyspell-notify-misspell start end word poss))))
1021 ;; return to original location
1f44857f 1022 (goto-char cursor-location)
60371a2e
RS
1023 (if ispell-quit (setq ispell-quit nil)))))))))
1024
3215afc4
GM
1025;*---------------------------------------------------------------------*/
1026;* flyspell-tex-math-initialized ... */
1027;*---------------------------------------------------------------------*/
1028(defvar flyspell-tex-math-initialized nil)
1029
1030;*---------------------------------------------------------------------*/
1031;* flyspell-math-tex-command-p ... */
1032;* ------------------------------------------------------------- */
1033;* This function uses the texmathp package to check if (point) */
1034;* is within a tex command. In order to avoid using */
1035;* condition-case each time we use the variable */
1036;* flyspell-tex-math-initialized to make a special case the first */
1037;* time that function is called. */
1038;*---------------------------------------------------------------------*/
1039(defun flyspell-math-tex-command-p ()
1040 (cond
1041 (flyspell-check-tex-math-command
1042 nil)
1043 ((eq flyspell-tex-math-initialized t)
1044 (texmathp))
1045 ((eq flyspell-tex-math-initialized 'error)
1046 nil)
1047 (t
1048 (setq flyspell-tex-math-initialized t)
1049 (condition-case nil
1050 (texmathp)
1051 (error (progn
1052 (setq flyspell-tex-math-initialized 'error)
1053 nil))))))
1054
60371a2e
RS
1055;*---------------------------------------------------------------------*/
1056;* flyspell-tex-command-p ... */
1057;*---------------------------------------------------------------------*/
1058(defun flyspell-tex-command-p (word)
0a67052f 1059 "Return t if WORD is a TeX command."
3215afc4
GM
1060 (or (save-excursion
1061 (let ((b (car (cdr word))))
1062 (and (re-search-backward "\\\\" (- (point) 100) t)
1063 (or (= (match-end 0) b)
1064 (and (goto-char (match-end 0))
1065 (looking-at flyspell-tex-command-regexp)
1066 (>= (match-end 0) b))))))
1067 (flyspell-math-tex-command-p)))
60371a2e
RS
1068
1069;*---------------------------------------------------------------------*/
1070;* flyspell-casechars-cache ... */
1071;*---------------------------------------------------------------------*/
1072(defvar flyspell-casechars-cache nil)
1073(defvar flyspell-ispell-casechars-cache nil)
1074(make-variable-buffer-local 'flyspell-casechars-cache)
1075(make-variable-buffer-local 'flyspell-ispell-casechars-cache)
1076
1077;*---------------------------------------------------------------------*/
1078;* flyspell-get-casechars ... */
1079;*---------------------------------------------------------------------*/
1080(defun flyspell-get-casechars ()
1081 "This function builds a string that is the regexp of word chars.
0a67052f
RS
1082In order to avoid one useless string construction,
1083this function changes the last char of the `ispell-casechars' string."
60371a2e
RS
1084 (let ((ispell-casechars (ispell-get-casechars)))
1085 (cond
3215afc4 1086 ((eq ispell-parser 'tex)
60371a2e
RS
1087 (setq flyspell-ispell-casechars-cache ispell-casechars)
1088 (setq flyspell-casechars-cache
1089 (concat (substring ispell-casechars
1090 0
1091 (- (length ispell-casechars) 1))
3215afc4 1092 "]"))
60371a2e
RS
1093 flyspell-casechars-cache)
1094 (t
1095 (setq flyspell-ispell-casechars-cache ispell-casechars)
1096 (setq flyspell-casechars-cache ispell-casechars)
1097 flyspell-casechars-cache))))
1098
1099;*---------------------------------------------------------------------*/
1100;* flyspell-get-not-casechars-cache ... */
1101;*---------------------------------------------------------------------*/
1102(defvar flyspell-not-casechars-cache nil)
1103(defvar flyspell-ispell-not-casechars-cache nil)
1104(make-variable-buffer-local 'flyspell-not-casechars-cache)
1105(make-variable-buffer-local 'flyspell-ispell-not-casechars-cache)
1106
1107;*---------------------------------------------------------------------*/
1108;* flyspell-get-not-casechars ... */
1109;*---------------------------------------------------------------------*/
1110(defun flyspell-get-not-casechars ()
1111 "This function builds a string that is the regexp of non-word chars."
1112 (let ((ispell-not-casechars (ispell-get-not-casechars)))
1113 (cond
3215afc4 1114 ((eq ispell-parser 'tex)
60371a2e
RS
1115 (setq flyspell-ispell-not-casechars-cache ispell-not-casechars)
1116 (setq flyspell-not-casechars-cache
1117 (concat (substring ispell-not-casechars
1118 0
1119 (- (length ispell-not-casechars) 1))
3215afc4 1120 "]"))
60371a2e
RS
1121 flyspell-not-casechars-cache)
1122 (t
1123 (setq flyspell-ispell-not-casechars-cache ispell-not-casechars)
1124 (setq flyspell-not-casechars-cache ispell-not-casechars)
1125 flyspell-not-casechars-cache))))
1126
1127;*---------------------------------------------------------------------*/
1128;* flyspell-get-word ... */
1129;*---------------------------------------------------------------------*/
1130(defun flyspell-get-word (following)
1131 "Return the word for spell-checking according to Ispell syntax.
3215afc4 1132If argument FOLLOWING is non-nil or if `ispell-following-word'
60371a2e
RS
1133is non-nil when called interactively, then the following word
1134\(rather than preceding\) is checked when the cursor is not over a word.
25f2ad05 1135Optional second argument contains other chars that can be included in word
60371a2e
RS
1136many times.
1137
1138Word syntax described by `ispell-dictionary-alist' (which see)."
1139 (let* ((flyspell-casechars (flyspell-get-casechars))
1140 (flyspell-not-casechars (flyspell-get-not-casechars))
1141 (ispell-otherchars (ispell-get-otherchars))
1142 (ispell-many-otherchars-p (ispell-get-many-otherchars-p))
3215afc4
GM
1143 (word-regexp (if (string< "" ispell-otherchars)
1144 (concat flyspell-casechars
1145 "+\\("
1146 ispell-otherchars
1147 "?"
1148 flyspell-casechars
1149 "+\\)"
1150 (if ispell-many-otherchars-p
1151 "*" "?"))
9658746b 1152 (concat flyspell-casechars "+")))
60371a2e
RS
1153 did-it-once
1154 start end word)
1155 ;; find the word
3215afc4 1156 (if (not (looking-at flyspell-casechars))
60371a2e
RS
1157 (if following
1158 (re-search-forward flyspell-casechars (point-max) t)
1159 (re-search-backward flyspell-casechars (point-min) t)))
1160 ;; move to front of word
1161 (re-search-backward flyspell-not-casechars (point-min) 'start)
3215afc4
GM
1162 (let ((pos nil))
1163 (if (string< "" ispell-otherchars)
9658746b
RS
1164 (while (and (looking-at ispell-otherchars)
1165 (not (bobp))
1166 (or (not did-it-once)
1167 ispell-many-otherchars-p)
1168 (not (eq pos (point))))
1169 (setq pos (point))
1170 (setq did-it-once t)
1171 (backward-char 1)
1172 (if (looking-at flyspell-casechars)
1173 (re-search-backward flyspell-not-casechars (point-min) 'move)
1174 (backward-char -1)))))
60371a2e 1175 ;; Now mark the word and save to string.
3215afc4 1176 (if (eq (re-search-forward word-regexp (point-max) t) nil)
60371a2e
RS
1177 nil
1178 (progn
1179 (setq start (match-beginning 0)
1180 end (point)
11570a8f 1181 word (buffer-substring-no-properties start end))
60371a2e
RS
1182 (list word start end)))))
1183
1184;*---------------------------------------------------------------------*/
3215afc4 1185;* flyspell-small-region ... */
60371a2e 1186;*---------------------------------------------------------------------*/
3215afc4 1187(defun flyspell-small-region (beg end)
60371a2e 1188 "Flyspell text between BEG and END."
60371a2e 1189 (save-excursion
3215afc4
GM
1190 (if (> beg end)
1191 (let ((old beg))
1192 (setq beg end)
1193 (setq end old)))
60371a2e 1194 (goto-char beg)
1d8a80f0
RS
1195 (let ((count 0))
1196 (while (< (point) end)
1197 (if (= count 100)
1198 (progn
1199 (message "Spell Checking...%d%%"
65a5c06a 1200 (* 100 (/ (float (- (point) beg)) (- end beg))))
1d8a80f0
RS
1201 (setq count 0))
1202 (setq count (+ 1 count)))
1203 (flyspell-word)
3215afc4 1204 (sit-for 0)
1d8a80f0
RS
1205 (let ((cur (point)))
1206 (forward-word 1)
1207 (if (and (< (point) end) (> (point) (+ cur 1)))
1208 (backward-char 1)))))
60371a2e 1209 (backward-char 1)
3215afc4 1210 (message "Spell Checking completed.")
60371a2e
RS
1211 (flyspell-word)))
1212
3215afc4
GM
1213;*---------------------------------------------------------------------*/
1214;* flyspell-external-ispell-process ... */
1215;*---------------------------------------------------------------------*/
1216(defvar flyspell-external-ispell-process '()
1f44857f 1217 "The external Flyspell Ispell process.")
3215afc4
GM
1218
1219;*---------------------------------------------------------------------*/
1220;* flyspell-external-ispell-buffer ... */
1221;*---------------------------------------------------------------------*/
1222(defvar flyspell-external-ispell-buffer '())
1223(defvar flyspell-large-region-buffer '())
1224(defvar flyspell-large-region-beg (point-min))
1225(defvar flyspell-large-region-end (point-max))
1226
1227;*---------------------------------------------------------------------*/
1228;* flyspell-external-point-words ... */
1229;*---------------------------------------------------------------------*/
1230(defun flyspell-external-point-words ()
1231 (let ((buffer flyspell-external-ispell-buffer))
1232 (set-buffer buffer)
1233 (beginning-of-buffer)
1234 (let ((size (- flyspell-large-region-end flyspell-large-region-beg))
1235 (start flyspell-large-region-beg))
1236 ;; now we are done with ispell, we have to find the word in
1237 ;; the initial buffer
1238 (while (< (point) (- (point-max) 1))
1239 ;; we have to fetch the incorrect word
1240 (if (re-search-forward "\\([^\n]+\\)\n" (point-max) t)
1241 (let ((word (match-string 1)))
1242 (goto-char (match-end 0))
1243 (set-buffer flyspell-large-region-buffer)
1244 (goto-char flyspell-large-region-beg)
1245 (message "Spell Checking...%d%% [%s]"
1246 (* 100 (/ (float (- (point) start)) size))
1247 word)
1248 (if (search-forward word flyspell-large-region-end t)
1249 (progn
1250 (setq flyspell-large-region-beg (point))
1251 (goto-char (- (point) 1))
1252 (flyspell-word)))
1253 (set-buffer buffer))
1254 (goto-char (point-max)))))
1255 ;; we are done
1256 (message "Spell Checking completed.")
1257 ;; ok, we are done with pointing out incorrect words, we just
1258 ;; have to kill the temporary buffer
1259 (kill-buffer flyspell-external-ispell-buffer)
1260 (setq flyspell-external-ispell-buffer nil)))
1261
1262;*---------------------------------------------------------------------*/
1263;* flyspell-large-region ... */
1264;*---------------------------------------------------------------------*/
1265(defun flyspell-large-region (beg end)
1266 (let* ((curbuf (current-buffer))
1267 (buffer (get-buffer-create "*flyspell-region*")))
1268 (setq flyspell-external-ispell-buffer buffer)
1269 (setq flyspell-large-region-buffer curbuf)
1270 (setq flyspell-large-region-beg beg)
1271 (setq flyspell-large-region-end end)
1272 (set-buffer buffer)
1273 (erase-buffer)
25f2ad05 1274 ;; this is done, we can start checking...
3215afc4
GM
1275 (message "Checking region...")
1276 (set-buffer curbuf)
1277 (let ((c (apply 'call-process-region beg
1278 end
1279 ispell-program-name
1280 nil
1281 buffer
1282 nil
1283 "-l"
1284 (let (args)
1285 ;; Local dictionary becomes the global dictionary in use.
1286 (if ispell-local-dictionary
1287 (setq ispell-dictionary ispell-local-dictionary))
1288 (setq args (ispell-get-ispell-args))
1289 (if ispell-dictionary ; use specified dictionary
1290 (setq args
1291 (append (list "-d" ispell-dictionary) args)))
1292 (if ispell-personal-dictionary ; use specified pers dict
1293 (setq args
1294 (append args
1295 (list "-p"
1296 (expand-file-name
1297 ispell-personal-dictionary)))))
1298 (setq args (append args ispell-extra-args))
1299 args))))
1300 (if (= c 0)
1301 (flyspell-external-point-words)
1302 (error "Can't check region...")))))
1303
1304;*---------------------------------------------------------------------*/
1305;* flyspell-region ... */
1306;* ------------------------------------------------------------- */
1307;* Because `ispell -a' is too slow, it is not possible to use */
1308;* it on large region. Then, when ispell is invoked on a large */
1309;* text region, a new `ispell -l' process is spawned. The */
1310;* pointed out words are then searched in the region a checked with */
1311;* regular flyspell means. */
1312;*---------------------------------------------------------------------*/
3bb710b0 1313;;;###autoload
3215afc4
GM
1314(defun flyspell-region (beg end)
1315 "Flyspell text between BEG and END."
1316 (interactive "r")
1317 (if (= beg end)
1318 ()
1319 (save-excursion
1320 (if (> beg end)
1321 (let ((old beg))
1322 (setq beg end)
1323 (setq end old)))
1324 (if (> (- end beg) flyspell-large-region)
1325 (flyspell-large-region beg end)
1326 (flyspell-small-region beg end)))))
1327
60371a2e
RS
1328;*---------------------------------------------------------------------*/
1329;* flyspell-buffer ... */
1330;*---------------------------------------------------------------------*/
3bb710b0 1331;;;###autoload
60371a2e
RS
1332(defun flyspell-buffer ()
1333 "Flyspell whole buffer."
1334 (interactive)
1335 (flyspell-region (point-min) (point-max)))
1336
3215afc4
GM
1337;*---------------------------------------------------------------------*/
1338;* old next error position ... */
1339;*---------------------------------------------------------------------*/
1340(defvar flyspell-old-buffer-error nil)
1341(defvar flyspell-old-pos-error nil)
1342
1343;*---------------------------------------------------------------------*/
1344;* flyspell-goto-next-error ... */
1345;*---------------------------------------------------------------------*/
1346(defun flyspell-goto-next-error ()
1347 "Go to the next previously detected error.
1348In general FLYSPELL-GOTO-NEXT-ERROR must be used after
1349FLYSPELL-BUFFER."
1350 (interactive)
1351 (let ((pos (point))
1352 (max (point-max)))
1353 (if (and (eq (current-buffer) flyspell-old-buffer-error)
1354 (eq pos flyspell-old-pos-error))
1355 (progn
1356 (if (= flyspell-old-pos-error max)
1357 ;; goto beginning of buffer
1358 (progn
1359 (message "Restarting from beginning of buffer")
1360 (goto-char (point-min)))
1361 (forward-word 1))
1362 (setq pos (point))))
1363 ;; seek the next error
1364 (while (and (< pos max)
1365 (let ((ovs (overlays-at pos))
1366 (r '()))
1367 (while (and (not r) (consp ovs))
1368 (if (flyspell-overlay-p (car ovs))
1369 (setq r t)
1370 (setq ovs (cdr ovs))))
1371 (not r)))
1372 (setq pos (1+ pos)))
25f2ad05 1373 ;; save the current location for next invocation
3215afc4
GM
1374 (setq flyspell-old-pos-error pos)
1375 (setq flyspell-old-buffer-error (current-buffer))
1376 (goto-char pos)
1377 (if (= pos max)
1378 (message "No more miss-spelled word!"))))
1379
60371a2e
RS
1380;*---------------------------------------------------------------------*/
1381;* flyspell-overlay-p ... */
1382;*---------------------------------------------------------------------*/
1383(defun flyspell-overlay-p (o)
1384 "A predicate that return true iff O is an overlay used by flyspell."
1385 (and (overlayp o) (overlay-get o 'flyspell-overlay)))
1386
1387;*---------------------------------------------------------------------*/
1388;* flyspell-delete-all-overlays ... */
1389;* ------------------------------------------------------------- */
1390;* Remove all the overlays introduced by flyspell. */
1391;*---------------------------------------------------------------------*/
1392(defun flyspell-delete-all-overlays ()
1393 "Delete all the overlays used by flyspell."
1394 (let ((l (overlays-in (point-min) (point-max))))
1395 (while (consp l)
1396 (progn
1397 (if (flyspell-overlay-p (car l))
1398 (delete-overlay (car l)))
1399 (setq l (cdr l))))))
1400
1401;*---------------------------------------------------------------------*/
1402;* flyspell-unhighlight-at ... */
1403;*---------------------------------------------------------------------*/
1404(defun flyspell-unhighlight-at (pos)
1405 "Remove the flyspell overlay that are located at POS."
1406 (if flyspell-persistent-highlight
1407 (let ((overlays (overlays-at pos)))
1408 (while (consp overlays)
1409 (if (flyspell-overlay-p (car overlays))
1410 (delete-overlay (car overlays)))
1411 (setq overlays (cdr overlays))))
3215afc4
GM
1412 (if (flyspell-overlay-p flyspell-overlay)
1413 (delete-overlay flyspell-overlay))))
60371a2e
RS
1414
1415;*---------------------------------------------------------------------*/
1416;* flyspell-properties-at-p ... */
1417;* ------------------------------------------------------------- */
1418;* Is there an highlight properties at position pos? */
1419;*---------------------------------------------------------------------*/
65a5c06a
RS
1420(defun flyspell-properties-at-p (pos)
1421 "Return t if there is a text property at POS, not counting `local-map'.
1422If variable `flyspell-highlight-properties' is set to nil,
1423text with properties are not checked. This function is used to discover
1424if the character at POS has any other property."
1425 (let ((prop (text-properties-at pos))
60371a2e
RS
1426 (keep t))
1427 (while (and keep (consp prop))
1428 (if (and (eq (car prop) 'local-map) (consp (cdr prop)))
1429 (setq prop (cdr (cdr prop)))
1430 (setq keep nil)))
1431 (consp prop)))
1432
1433;*---------------------------------------------------------------------*/
1434;* make-flyspell-overlay ... */
1435;*---------------------------------------------------------------------*/
1436(defun make-flyspell-overlay (beg end face mouse-face)
0a67052f
RS
1437 "Allocate an overlay to highlight an incorrect word.
1438BEG and END specify the range in the buffer of that word.
1439FACE and MOUSE-FACE specify the `face' and `mouse-face' properties
1440for the overlay."
739394c7 1441 (let ((flyspell-overlay (make-overlay beg end nil t nil)))
60371a2e
RS
1442 (overlay-put flyspell-overlay 'face face)
1443 (overlay-put flyspell-overlay 'mouse-face mouse-face)
1444 (overlay-put flyspell-overlay 'flyspell-overlay t)
dd0ffc28 1445 (if flyspell-use-local-map
60371a2e
RS
1446 (overlay-put flyspell-overlay
1447 flyspell-overlay-keymap-property-name
0dd10e62 1448 flyspell-mouse-map))
3215afc4 1449 flyspell-overlay))
60371a2e
RS
1450
1451;*---------------------------------------------------------------------*/
1452;* flyspell-highlight-incorrect-region ... */
1453;*---------------------------------------------------------------------*/
3215afc4 1454(defun flyspell-highlight-incorrect-region (beg end poss)
0a67052f 1455 "Set up an overlay on a misspelled word, in the buffer from BEG to END."
3215afc4
GM
1456 (unless (run-hook-with-args-until-success
1457 'flyspell-incorrect-hook beg end poss)
1458 (if (or flyspell-highlight-properties (not (flyspell-properties-at-p beg)))
1459 (progn
1460 ;; we cleanup current overlay at the same position
1461 (if (and (not flyspell-persistent-highlight)
1462 (overlayp flyspell-overlay))
1463 (delete-overlay flyspell-overlay)
1464 (let ((overlays (overlays-at beg)))
1465 (while (consp overlays)
1466 (if (flyspell-overlay-p (car overlays))
1467 (delete-overlay (car overlays)))
1468 (setq overlays (cdr overlays)))))
1469 ;; now we can use a new overlay
1470 (setq flyspell-overlay
1471 (make-flyspell-overlay beg end
1472 'flyspell-incorrect-face 'highlight))))))
60371a2e
RS
1473
1474;*---------------------------------------------------------------------*/
1475;* flyspell-highlight-duplicate-region ... */
1476;*---------------------------------------------------------------------*/
1477(defun flyspell-highlight-duplicate-region (beg end)
0a67052f 1478 "Set up an overlay on a duplicated word, in the buffer from BEG to END."
65a5c06a 1479 (if (or flyspell-highlight-properties (not (flyspell-properties-at-p beg)))
60371a2e
RS
1480 (progn
1481 ;; we cleanup current overlay at the same position
1482 (if (and (not flyspell-persistent-highlight)
1483 (overlayp flyspell-overlay))
1484 (delete-overlay flyspell-overlay)
1485 (let ((overlays (overlays-at beg)))
1486 (while (consp overlays)
1487 (if (flyspell-overlay-p (car overlays))
1488 (delete-overlay (car overlays)))
1489 (setq overlays (cdr overlays)))))
1490 ;; now we can use a new overlay
1491 (setq flyspell-overlay
1492 (make-flyspell-overlay beg end
1493 'flyspell-duplicate-face 'highlight)))))
1494
1495;*---------------------------------------------------------------------*/
1496;* flyspell-auto-correct-cache ... */
1497;*---------------------------------------------------------------------*/
1498(defvar flyspell-auto-correct-pos nil)
1499(defvar flyspell-auto-correct-region nil)
1500(defvar flyspell-auto-correct-ring nil)
3215afc4
GM
1501(defvar flyspell-auto-correct-word nil)
1502(make-variable-buffer-local 'flyspell-auto-correct-pos)
1503(make-variable-buffer-local 'flyspell-auto-correct-region)
1504(make-variable-buffer-local 'flyspell-auto-correct-ring)
1505(make-variable-buffer-local 'flyspell-auto-correct-word)
60371a2e
RS
1506
1507;*---------------------------------------------------------------------*/
3215afc4 1508;* flyspell-check-previous-highlighted-word ... */
60371a2e 1509;*---------------------------------------------------------------------*/
3215afc4 1510(defun flyspell-check-previous-highlighted-word (&optional arg)
25f2ad05 1511 "Correct the closer misspelled word.
3215afc4
GM
1512This function scans a mis-spelled word before the cursor. If it finds one
1513it proposes replacement for that word. With prefix arg, count that many
1514misspelled words backwards."
1515 (interactive)
1516 (let ((pos1 (point))
1517 (pos (point))
1518 (arg (if (or (not (numberp arg)) (< arg 1)) 1 arg))
1519 ov ovs)
1520 (if (catch 'exit
1521 (while (and (setq pos (previous-overlay-change pos))
1522 (not (= pos pos1)))
1523 (setq pos1 pos)
1524 (if (> pos (point-min))
1525 (progn
1526 (setq ovs (overlays-at (1- pos)))
1527 (while (consp ovs)
1528 (setq ov (car ovs))
1529 (setq ovs (cdr ovs))
1530 (if (and (overlay-get ov 'flyspell-overlay)
1531 (= 0 (setq arg (1- arg))))
1532 (throw 'exit t)))))))
60371a2e 1533 (save-excursion
3215afc4
GM
1534 (goto-char pos)
1535 (ispell-word))
1f44857f 1536 (error "No word to correct before point"))))
3215afc4
GM
1537
1538;*---------------------------------------------------------------------*/
1539;* flyspell-display-next-corrections ... */
1540;*---------------------------------------------------------------------*/
1541(defun flyspell-display-next-corrections (corrections)
1542 (let ((string "Corrections:")
1543 (l corrections)
1544 (pos '()))
1545 (while (< (length string) 80)
1546 (if (equal (car l) flyspell-auto-correct-word)
1547 (setq pos (cons (+ 1 (length string)) pos)))
1548 (setq string (concat string " " (car l)))
1549 (setq l (cdr l)))
1550 (while (consp pos)
1551 (let ((num (car pos)))
1552 (put-text-property num
1553 (+ num (length flyspell-auto-correct-word))
1554 'face
1555 'flyspell-incorrect-face
1556 string))
1557 (setq pos (cdr pos)))
1558 (if (fboundp 'display-message)
1559 (display-message 'no-log string)
1560 (message string))))
1561
1562;*---------------------------------------------------------------------*/
1563;* flyspell-abbrev-table ... */
1564;*---------------------------------------------------------------------*/
1565(defun flyspell-abbrev-table ()
1566 (if flyspell-use-global-abbrev-table-p
1567 global-abbrev-table
1568 local-abbrev-table))
1569
1570;*---------------------------------------------------------------------*/
1571;* flyspell-auto-correct-word ... */
1572;*---------------------------------------------------------------------*/
1573(defun flyspell-auto-correct-word ()
1574 "Correct the current word.
1575This command proposes various successive corrections for the current word."
1576 (interactive)
1577 (let ((pos (point))
1578 (old-max (point-max)))
1579 ;; use the correct dictionary
1580 (flyspell-accept-buffer-local-defs)
1581 (if (and (eq flyspell-auto-correct-pos pos)
1582 (consp flyspell-auto-correct-region))
1583 ;; we have already been using the function at the same location
1584 (let* ((start (car flyspell-auto-correct-region))
1585 (len (cdr flyspell-auto-correct-region)))
1586 (delete-region start (+ start len))
1587 (setq flyspell-auto-correct-ring (cdr flyspell-auto-correct-ring))
1588 (let* ((word (car flyspell-auto-correct-ring))
1589 (len (length word)))
1590 (rplacd flyspell-auto-correct-region len)
1591 (goto-char start)
1592 (if flyspell-abbrev-p
1593 (if (flyspell-already-abbrevp (flyspell-abbrev-table)
1594 flyspell-auto-correct-word)
1595 (flyspell-change-abbrev (flyspell-abbrev-table)
1596 flyspell-auto-correct-word
1597 word)
1598 (define-abbrev (flyspell-abbrev-table)
1599 flyspell-auto-correct-word word)))
1600 (insert word)
1601 (flyspell-word)
1602 (flyspell-display-next-corrections flyspell-auto-correct-ring))
1603 (flyspell-ajust-cursor-point pos (point) old-max)
1604 (setq flyspell-auto-correct-pos (point)))
1605 ;; fetch the word to be checked
1606 (let ((word (flyspell-get-word nil))
1607 start end poss)
1608 ;; destructure return word info list.
1609 (setq start (car (cdr word))
1610 end (car (cdr (cdr word)))
1611 word (car word))
1612 (setq flyspell-auto-correct-word word)
1613 ;; now check spelling of word.
1614 (process-send-string ispell-process "%\n") ;put in verbose mode
1615 (process-send-string ispell-process (concat "^" word "\n"))
1616 ;; wait until ispell has processed word
1617 (while (progn
1618 (accept-process-output ispell-process)
1619 (not (string= "" (car ispell-filter)))))
1620 (setq ispell-filter (cdr ispell-filter))
1621 (if (consp ispell-filter)
1622 (setq poss (ispell-parse-output (car ispell-filter))))
1623 (cond ((or (eq poss t) (stringp poss))
1624 ;; don't correct word
1625 t)
1626 ((null poss)
1627 ;; ispell error
1628 (error "Ispell: error in Ispell process"))
1629 (t
1630 ;; the word is incorrect, we have to propose a replacement
1631 (let ((replacements (if flyspell-sort-corrections
1632 (sort (car (cdr (cdr poss))) 'string<)
1633 (car (cdr (cdr poss))))))
1634 (setq flyspell-auto-correct-region nil)
1635 (if (consp replacements)
1636 (progn
1637 (let ((replace (car replacements)))
1638 (let ((new-word replace))
1639 (if (not (equal new-word (car poss)))
1640 (progn
1641 ;; the save the current replacements
1642 (setq flyspell-auto-correct-region
1643 (cons start (length new-word)))
1644 (let ((l replacements))
1645 (while (consp (cdr l))
1646 (setq l (cdr l)))
1647 (rplacd l (cons (car poss) replacements)))
1648 (setq flyspell-auto-correct-ring
1649 replacements)
1650 (delete-region start end)
1651 (insert new-word)
1652 (if flyspell-abbrev-p
1653 (if (flyspell-already-abbrevp
1654 (flyspell-abbrev-table) word)
1655 (flyspell-change-abbrev
1656 (flyspell-abbrev-table)
1657 word
1658 new-word)
1659 (define-abbrev (flyspell-abbrev-table)
1660 word new-word)))
1661 (flyspell-word)
1662 (flyspell-display-next-corrections
1663 (cons new-word flyspell-auto-correct-ring))
1664 (flyspell-ajust-cursor-point pos
1665 (point)
1666 old-max))))))))))
1667 (setq flyspell-auto-correct-pos (point))
1668 (ispell-pdict-save t)))))
a63f25a4 1669
60371a2e
RS
1670;*---------------------------------------------------------------------*/
1671;* flyspell-correct-word ... */
1672;*---------------------------------------------------------------------*/
1673(defun flyspell-correct-word (event)
0a67052f
RS
1674 "Pop up a menu of possible corrections for a misspelled word.
1675The word checked is the word at the mouse position."
60371a2e
RS
1676 (interactive "e")
1677 ;; use the correct dictionary
3215afc4 1678 (flyspell-accept-buffer-local-defs)
60371a2e
RS
1679 ;; retain cursor location (I don't know why but save-excursion here fails).
1680 (let ((save (point)))
1681 (mouse-set-point event)
1682 (let ((cursor-location (point))
1683 (word (flyspell-get-word nil))
1684 start end poss replace)
1685 ;; destructure return word info list.
1686 (setq start (car (cdr word))
1687 end (car (cdr (cdr word)))
1688 word (car word))
1689 ;; now check spelling of word.
1690 (process-send-string ispell-process "%\n") ;put in verbose mode
1691 (process-send-string ispell-process (concat "^" word "\n"))
1692 ;; wait until ispell has processed word
1693 (while (progn
1694 (accept-process-output ispell-process)
1695 (not (string= "" (car ispell-filter)))))
1696 (setq ispell-filter (cdr ispell-filter))
3215afc4 1697 (if (consp ispell-filter)
60371a2e
RS
1698 (setq poss (ispell-parse-output (car ispell-filter))))
1699 (cond ((or (eq poss t) (stringp poss))
1700 ;; don't correct word
1701 t)
1702 ((null poss)
1703 ;; ispell error
1704 (error "Ispell: error in Ispell process"))
1705 ((string-match "GNU" (emacs-version))
1706 ;; the word is incorrect, we have to propose a replacement
0a67052f 1707 (setq replace (flyspell-emacs-popup event poss word))
60371a2e 1708 (cond ((eq replace 'ignore)
3215afc4 1709 (goto-char save)
60371a2e
RS
1710 nil)
1711 ((eq replace 'save)
3215afc4 1712 (goto-char save)
60371a2e
RS
1713 (process-send-string ispell-process (concat "*" word "\n"))
1714 (flyspell-unhighlight-at cursor-location)
1715 (setq ispell-pdict-modified-p '(t)))
1716 ((or (eq replace 'buffer) (eq replace 'session))
1717 (process-send-string ispell-process (concat "@" word "\n"))
1718 (if (null ispell-pdict-modified-p)
1719 (setq ispell-pdict-modified-p
1720 (list ispell-pdict-modified-p)))
1721 (flyspell-unhighlight-at cursor-location)
3215afc4 1722 (goto-char save)
60371a2e
RS
1723 (if (eq replace 'buffer)
1724 (ispell-add-per-file-word-list word)))
1725 (replace
3215afc4
GM
1726 (let ((new-word (if (atom replace)
1727 replace
1728 (car replace)))
1729 (cursor-location (+ (- (length word) (- end start))
1730 cursor-location)))
1731 (if (not (equal new-word (car poss)))
1732 (let ((old-max (point-max)))
1733 (delete-region start end)
1734 (insert new-word)
1735 (if flyspell-abbrev-p
1736 (define-abbrev (flyspell-abbrev-table)
1737 word
1738 new-word))
1739 (flyspell-ajust-cursor-point save
1740 cursor-location
1741 old-max)))))
1742 (t
1743 (goto-char save)
1744 nil)))
2be80b63 1745 ((eq flyspell-emacs 'xemacs)
60371a2e 1746 (flyspell-xemacs-popup
3215afc4
GM
1747 event poss word cursor-location start end save)
1748 (goto-char save)))
1749 (ispell-pdict-save t))))
60371a2e
RS
1750
1751;*---------------------------------------------------------------------*/
1752;* flyspell-xemacs-correct ... */
1753;*---------------------------------------------------------------------*/
3215afc4 1754(defun flyspell-xemacs-correct (replace poss word cursor-location start end save)
60371a2e
RS
1755 "The xemacs popup menu callback."
1756 (cond ((eq replace 'ignore)
1757 nil)
1758 ((eq replace 'save)
1759 (process-send-string ispell-process (concat "*" word "\n"))
3215afc4 1760 (process-send-string ispell-process "#\n")
60371a2e
RS
1761 (flyspell-unhighlight-at cursor-location)
1762 (setq ispell-pdict-modified-p '(t)))
1763 ((or (eq replace 'buffer) (eq replace 'session))
1764 (process-send-string ispell-process (concat "@" word "\n"))
1765 (flyspell-unhighlight-at cursor-location)
1766 (if (null ispell-pdict-modified-p)
1767 (setq ispell-pdict-modified-p
1768 (list ispell-pdict-modified-p)))
1769 (if (eq replace 'buffer)
1770 (ispell-add-per-file-word-list word)))
1771 (replace
3215afc4
GM
1772 (let ((old-max (point-max))
1773 (new-word (if (atom replace)
1774 replace
1775 (car replace)))
1776 (cursor-location (+ (- (length word) (- end start))
1777 cursor-location)))
1778 (if (not (equal new-word (car poss)))
1779 (progn
1780 (delete-region start end)
1781 (goto-char start)
1782 (insert new-word)
1783 (if flyspell-abbrev-p
1784 (define-abbrev (flyspell-abbrev-table)
1785 word
1786 new-word))))
1787 (flyspell-ajust-cursor-point save cursor-location old-max)))))
1788
1789;*---------------------------------------------------------------------*/
1790;* flyspell-ajust-cursor-point ... */
1791;*---------------------------------------------------------------------*/
1792(defun flyspell-ajust-cursor-point (save cursor-location old-max)
1793 (if (>= save cursor-location)
1794 (let ((new-pos (+ save (- (point-max) old-max))))
1795 (goto-char (cond
1796 ((< new-pos (point-min))
1797 (point-min))
1798 ((> new-pos (point-max))
1799 (point-max))
1800 (t new-pos))))
1801 (goto-char save)))
60371a2e
RS
1802
1803;*---------------------------------------------------------------------*/
65a5c06a 1804;* flyspell-emacs-popup ... */
60371a2e 1805;*---------------------------------------------------------------------*/
0a67052f
RS
1806(defun flyspell-emacs-popup (event poss word)
1807 "The Emacs popup menu."
60371a2e
RS
1808 (if (not event)
1809 (let* ((mouse-pos (mouse-position))
1810 (mouse-pos (if (nth 1 mouse-pos)
1811 mouse-pos
1812 (set-mouse-position (car mouse-pos)
3215afc4 1813 (/ (frame-width) 2) 2)
60371a2e
RS
1814 (unfocus-frame)
1815 (mouse-position))))
1816 (setq event (list (list (car (cdr mouse-pos))
1817 (1+ (cdr (cdr mouse-pos))))
1818 (car mouse-pos)))))
1819 (let* ((corrects (if flyspell-sort-corrections
1820 (sort (car (cdr (cdr poss))) 'string<)
1821 (car (cdr (cdr poss)))))
1822 (cor-menu (if (consp corrects)
1823 (mapcar (lambda (correct)
1824 (list correct correct))
1825 corrects)
1826 '()))
1827 (affix (car (cdr (cdr (cdr poss)))))
1828 (base-menu (let ((save (if (consp affix)
1829 (list
1830 (list (concat "Save affix: " (car affix))
1831 'save)
1832 '("Accept (session)" accept)
1833 '("Accept (buffer)" buffer))
1834 '(("Save word" save)
1835 ("Accept (session)" session)
1836 ("Accept (buffer)" buffer)))))
1837 (if (consp cor-menu)
1838 (append cor-menu (cons "" save))
1839 save)))
1840 (menu (cons "flyspell correction menu" base-menu)))
1841 (car (x-popup-menu event
1842 (list (format "%s [%s]" word (or ispell-local-dictionary
1843 ispell-dictionary))
1844 menu)))))
1845
1846;*---------------------------------------------------------------------*/
65a5c06a 1847;* flyspell-xemacs-popup ... */
60371a2e 1848;*---------------------------------------------------------------------*/
3215afc4 1849(defun flyspell-xemacs-popup (event poss word cursor-location start end save)
1f44857f 1850 "The XEmacs popup menu."
60371a2e
RS
1851 (let* ((corrects (if flyspell-sort-corrections
1852 (sort (car (cdr (cdr poss))) 'string<)
1853 (car (cdr (cdr poss)))))
1854 (cor-menu (if (consp corrects)
1855 (mapcar (lambda (correct)
1856 (vector correct
1857 (list 'flyspell-xemacs-correct
1858 correct
1859 (list 'quote poss)
1860 word
1861 cursor-location
1862 start
3215afc4
GM
1863 end
1864 save)
60371a2e
RS
1865 t))
1866 corrects)
1867 '()))
1868 (affix (car (cdr (cdr (cdr poss)))))
1869 (menu (let ((save (if (consp affix)
1870 (vector
1871 (concat "Save affix: " (car affix))
1872 (list 'flyspell-xemacs-correct
1873 ''save
1874 (list 'quote poss)
1875 word
1876 cursor-location
1877 start
3215afc4
GM
1878 end
1879 save)
60371a2e
RS
1880 t)
1881 (vector
1882 "Save word"
1883 (list 'flyspell-xemacs-correct
1884 ''save
1885 (list 'quote poss)
1886 word
1887 cursor-location
1888 start
3215afc4
GM
1889 end
1890 save)
60371a2e
RS
1891 t)))
1892 (session (vector "Accept (session)"
1893 (list 'flyspell-xemacs-correct
1894 ''session
1895 (list 'quote poss)
1896 word
1897 cursor-location
1898 start
3215afc4
GM
1899 end
1900 save)
60371a2e
RS
1901 t))
1902 (buffer (vector "Accept (buffer)"
1903 (list 'flyspell-xemacs-correct
1904 ''buffer
1905 (list 'quote poss)
1906 word
1907 cursor-location
1908 start
3215afc4
GM
1909 end
1910 save)
60371a2e
RS
1911 t)))
1912 (if (consp cor-menu)
1913 (append cor-menu (list "-" save session buffer))
1914 (list save session buffer)))))
1915 (popup-menu (cons (format "%s [%s]" word (or ispell-local-dictionary
1916 ispell-dictionary))
1917 menu))))
1918
3215afc4 1919;*---------------------------------------------------------------------*/
25f2ad05 1920;* Some example functions for real autocorrecting */
3215afc4 1921;*---------------------------------------------------------------------*/
25f2ad05 1922
3215afc4 1923(defun flyspell-maybe-correct-transposition (beg end poss)
25f2ad05
GM
1924 "Check replacements for transposed characters.
1925
1926If the text between BEG and END is equal to a correction suggested by
1927Ispell, after transposing two adjacent characters, correct the text,
1928and return t.
1929
1930The third arg POSS is either the symbol 'doublon' or a list of
1931possible corrections as returned by 'ispell-parse-output'.
3215afc4
GM
1932
1933This function is meant to be added to 'flyspell-incorrect-hook'."
25f2ad05
GM
1934 (when (consp poss)
1935 (let ((temp-buffer (get-buffer-create " *flyspell-temp*"))
1936 found)
1937 (save-excursion
1938 (copy-to-buffer temp-buffer beg end)
1939 (set-buffer temp-buffer)
1940 (goto-char (1+ (point-min)))
1941 (while (and (not (eobp)) (not found))
1942 (transpose-chars 1)
1943 (if (member (buffer-string) (nth 2 poss))
1944 (setq found (point))
1945 (transpose-chars -1)
1946 (forward-char))))
1947 (when found
f216a6b3 1948 (save-excursion
25f2ad05
GM
1949 (goto-char (+ beg found -1))
1950 (transpose-chars -1)
1951 t)))))
3215afc4
GM
1952
1953(defun flyspell-maybe-correct-doubling (beg end poss)
25f2ad05
GM
1954 "Check replacements for doubled characters.
1955
1956If the text between BEG and END is equal to a correction suggested by
1957Ispell, after removing a pair of doubled characters, correct the text,
1958and return t.
1959
1960The third arg POSS is either the symbol 'doublon' or a list of
1961possible corrections as returned by 'ispell-parse-output'.
3215afc4
GM
1962
1963This function is meant to be added to 'flyspell-incorrect-hook'."
1f44857f 1964 (when (consp poss)
25f2ad05
GM
1965 (let ((temp-buffer (get-buffer-create " *flyspell-temp*"))
1966 found)
1967 (save-excursion
1968 (copy-to-buffer temp-buffer beg end)
1969 (set-buffer temp-buffer)
1970 (goto-char (1+ (point-min)))
1971 (while (and (not (eobp)) (not found))
1972 (when (char-equal (char-after) (char-before))
1973 (delete-char 1)
1974 (if (member (buffer-string) (nth 2 poss))
1975 (setq found (point))
1976 (insert-char (char-before) 1)))
1977 (forward-char)))
1978 (when found
f216a6b3 1979 (save-excursion
25f2ad05
GM
1980 (goto-char (+ beg found -1))
1981 (delete-char 1)
1982 t)))))
3215afc4
GM
1983
1984;*---------------------------------------------------------------------*/
1985;* flyspell-already-abbrevp ... */
1986;*---------------------------------------------------------------------*/
1987(defun flyspell-already-abbrevp (table word)
1988 (let ((sym (abbrev-symbol word table)))
1989 (and sym (symbolp sym))))
60371a2e 1990
3215afc4
GM
1991;*---------------------------------------------------------------------*/
1992;* flyspell-change-abbrev ... */
1993;*---------------------------------------------------------------------*/
1994(defun flyspell-change-abbrev (table old new)
1995 (set (abbrev-symbol old table) new))
1996
1997(provide 'flyspell)
e8af40ee 1998
60371a2e 1999;;; flyspell.el ends here