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