]> code.delx.au - gnu-emacs/blobdiff - lisp/textmodes/flyspell.el
(flyspell-mark-duplications-exceptions): New variable. List of
[gnu-emacs] / lisp / textmodes / flyspell.el
index ebee4691e8c7921ad6dff1a5a9da4e9fc83679cf..9d5c7868d13bf917132ad9127b324a4d87c369a3 100644 (file)
@@ -1,7 +1,7 @@
 ;;; flyspell.el --- on-the-fly spell checker
 
-;; Copyright (C) 1998, 2000, 2002, 2003, 2004,
-;;   2005, 2006 Free Software Foundation, Inc.
+;; Copyright (C) 1998, 2000, 2001, 2002, 2003, 2004,
+;;   2005, 2006, 2007 Free Software Foundation, Inc.
 
 ;; Author: Manuel Serrano <Manuel.Serrano@sophia.inria.fr>
 ;; Maintainer: FSF
@@ -11,7 +11,7 @@
 
 ;; GNU Emacs is free software; you can redistribute it and/or modify
 ;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation; either version 2, or (at your option)
+;; the Free Software Foundation; either version 3, or (at your option)
 ;; any later version.
 
 ;; GNU Emacs is distributed in the hope that it will be useful,
@@ -67,11 +67,21 @@ Non-nil means use highlight, nil means use minibuffer messages."
 
 (defcustom flyspell-mark-duplications-flag t
   "Non-nil means Flyspell reports a repeated word as an error.
+See `flyspell-mark-duplications-exceptions' to add exceptions to this rule.
 Detection of repeated words is not implemented in
 \"large\" regions; see `flyspell-large-region'."
   :group 'flyspell
   :type 'boolean)
 
+(defcustom flyspell-mark-duplications-exceptions
+  '(("francais" . ("nous" "vous")))
+  "A list of exceptions for duplicated words.
+It should be a list of (LANGUAGE . EXCEPTION-LIST).  LANGUAGE is matched
+against the current dictionary and EXCEPTION-LIST is a list of strings.
+The duplicated word is downcased before it is compared with the exceptions."
+  :group 'flyspell
+  :type '(alist :key-type string :value-type (repeat string)))
+
 (defcustom flyspell-sort-corrections nil
   "Non-nil means, sort the corrections alphabetically before popping them."
   :group 'flyspell
@@ -189,7 +199,7 @@ Ispell's ultimate default dictionary."
   :type 'string)
 
 (defcustom flyspell-check-tex-math-command nil
-  "Non nil means check even inside TeX math environment.
+  "Non-nil means check even inside TeX math environment.
 TeX math environments are discovered by the TEXMATHP that implemented
 inside the texmathp.el Emacs package.  That package may be found at:
 http://strw.leidenuniv.nl/~dominik/Tools"
@@ -412,6 +422,7 @@ property of the major mode name.")
     (define-key map flyspell-auto-correct-binding 'flyspell-auto-correct-previous-word)
     (define-key map [(control ?\,)] 'flyspell-goto-next-error)
     (define-key map [(control ?\.)] 'flyspell-auto-correct-word)
+    (define-key map [?\C-c ?$] 'flyspell-correct-word-before-point)
     map)
   "Minor mode keymap for Flyspell mode--for the whole buffer.")
 
@@ -430,7 +441,7 @@ property of the major mode name.")
 (defface flyspell-incorrect
   '((((class color)) (:foreground "OrangeRed" :bold t :underline t))
     (t (:bold t)))
-  "Face used to display a misspelled word in Flyspell."
+  "Face used for marking a misspelled word in Flyspell."
   :group 'flyspell)
 ;; backward-compatibility alias
 (put 'flyspell-incorrect-face 'face-alias 'flyspell-incorrect)
@@ -438,7 +449,7 @@ property of the major mode name.")
 (defface flyspell-duplicate
   '((((class color)) (:foreground "Gold3" :bold t :underline t))
     (t (:bold t)))
-  "Face used to display subsequent occurrences of a misspelled word.
+  "Face used for marking a misspelled word that appears twice in the buffer.
 See also `flyspell-duplicate-distance'."
   :group 'flyspell)
 ;; backward-compatibility alias
@@ -456,7 +467,8 @@ See also `flyspell-duplicate-distance'."
 This spawns a single Ispell process and checks each word.
 The default flyspell behavior is to highlight incorrect words.
 With no argument, this command toggles Flyspell mode.
-With a prefix argument ARG, turn Flyspell minor mode on iff ARG is positive.
+With a prefix argument ARG, turn Flyspell minor mode on if ARG is positive,
+otherwise turn it off.
 
 Bindings:
 \\[ispell-word]: correct words (using Ispell).
@@ -540,6 +552,11 @@ in your .emacs file.
             (member (or ispell-local-dictionary ispell-dictionary)
                     flyspell-dictionaries-that-consider-dash-as-word-delimiter)))))
 
+(defun flyspell-hack-local-variables-hook ()
+  ;; When local variables are loaded, see if the dictionary context
+  ;; has changed.
+  (flyspell-accept-buffer-local-defs 'force))
+
 (defun flyspell-kill-ispell-hook ()
   (setq flyspell-last-buffer nil)
   (dolist (buf (buffer-list))
@@ -578,6 +595,9 @@ in your .emacs file.
   (add-hook 'pre-command-hook (function flyspell-pre-command-hook) t t)
   ;; we bound flyspell action to after-change hook
   (add-hook 'after-change-functions 'flyspell-after-change-function nil t)
+  ;; we bound flyspell action to hack-local-variables-hook
+  (add-hook 'hack-local-variables-hook
+           (function flyspell-hack-local-variables-hook) t t)
   ;; set flyspell-generic-check-word-predicate based on the major mode
   (let ((mode-predicate (get major-mode 'flyspell-mode-predicate)))
     (if mode-predicate
@@ -683,6 +703,8 @@ not the very same deplacement command."
   (remove-hook 'post-command-hook (function flyspell-post-command-hook) t)
   (remove-hook 'pre-command-hook (function flyspell-pre-command-hook) t)
   (remove-hook 'after-change-functions 'flyspell-after-change-function t)
+  (remove-hook 'hack-local-variables-hook
+              (function flyspell-hack-local-variables-hook) t)
   ;; we remove all the flyspell hilightings
   (flyspell-delete-all-overlays)
   ;; we have to erase pre cache variables
@@ -959,6 +981,7 @@ Mostly we check word delimiters."
 (defun flyspell-word-search-backward (word bound)
   (save-excursion
     (let ((r '())
+         (inhibit-point-motion-hooks t)
          p)
       (while (and (not r) (setq p (search-backward word bound t)))
        (let ((lw (flyspell-get-word '())))
@@ -973,6 +996,7 @@ Mostly we check word delimiters."
 (defun flyspell-word-search-forward (word bound)
   (save-excursion
     (let ((r '())
+         (inhibit-point-motion-hooks t)
          p)
       (while (and (not r) (setq p (search-forward word bound t)))
        (let ((lw (flyspell-get-word '())))
@@ -1008,12 +1032,22 @@ Mostly we check word delimiters."
                     (and (> start (point-min))
                          (not (memq (char-after (1- start)) '(?\} ?\\)))))
                 flyspell-mark-duplications-flag
+                (not (catch 'exception
+                       (dolist (except flyspell-mark-duplications-exceptions)
+                         (and (string= (or ispell-local-dictionary
+                                           ispell-dictionary)
+                                       (car except))
+                              (member (downcase word) (cdr except))
+                              (throw 'exception t)))))
                 (save-excursion
-                  (goto-char (1- start))
-                  (let ((p (flyspell-word-search-backward
-                            word
-                            (- start (1+ (- end start))))))
-                    (and p (/= p (1- start))))))
+                  (goto-char start)
+                  (let* ((bound
+                          (- start
+                             (- end start)
+                             (- (skip-chars-backward " \t\n\f"))))
+                         (p (when (>= bound (point-min))
+                              (flyspell-word-search-backward word bound))))
+                    (and p (/= p start)))))
            ;; yes, this is a doublon
            (flyspell-highlight-incorrect-region start end 'doublon)
            nil)
@@ -1470,7 +1504,7 @@ The buffer to mark them in is `flyspell-large-region-buffer'."
     (flyspell-word)     ; Make sure current word is checked
     (backward-word 1)
     (while (and (< (point) end)
-               (re-search-forward "\\b\\([^ \n\t]+\\)[ \n\t]+\\1\\b"
+               (re-search-forward "\\<\\(\\w+\\)\\>[ \n\t\f]+\\1\\>"
                                   end 'move))
       (flyspell-word)
       (backward-word 1))
@@ -1493,7 +1527,7 @@ The buffer to mark them in is `flyspell-large-region-buffer'."
     (if flyspell-issue-message-flag (message "Checking region..."))
     (set-buffer curbuf)
     (ispell-check-version)
-    (let ((c (apply 'call-process-region beg
+    (let ((c (apply 'ispell-call-process-region beg
                    end
                    ispell-program-name
                    nil
@@ -1605,7 +1639,7 @@ FLYSPELL-BUFFER."
 ;;*    flyspell-overlay-p ...                                           */
 ;;*---------------------------------------------------------------------*/
 (defun flyspell-overlay-p (o)
-  "A predicate that return true iff O is an overlay used by flyspell."
+  "Return true if O is an overlay used by flyspell."
   (and (overlayp o) (overlay-get o 'flyspell-overlay)))
 
 ;;*---------------------------------------------------------------------*/
@@ -1706,7 +1740,9 @@ is itself incorrect, but suspiciously repeated."
            ;; now we can use a new overlay
            (setq flyspell-overlay
                  (make-flyspell-overlay
-                  beg end 'flyspell-incorrect 'highlight)))))))
+                  beg end
+                  (if (eq poss 'doublon) 'flyspell-duplicate 'flyspell-incorrect)
+                  'highlight)))))))
 
 ;;*---------------------------------------------------------------------*/
 ;;*    flyspell-highlight-duplicate-region ...                          */
@@ -1809,7 +1845,7 @@ misspelled words backwards."
 (defun flyspell-define-abbrev (name expansion)
   (let ((table (flyspell-abbrev-table)))
     (when table
-      (define-abbrev table name expansion))))
+      (define-abbrev table (downcase name) expansion))))
 
 ;;*---------------------------------------------------------------------*/
 ;;*    flyspell-auto-correct-word ...                                   */
@@ -1943,12 +1979,8 @@ Sets `flyspell-auto-correct-previous-pos' to nil"
 But don't look beyond what's visible on the screen."
   (interactive "d")
 
-  (let (top bot)
-    (save-excursion
-      (move-to-window-line 0)
-      (setq top (point))
-      (move-to-window-line -1)
-      (setq bot (point)))
+  (let ((top (window-start))
+       (bot (window-end)))
     (save-excursion
       (save-restriction
        (narrow-to-region top bot)
@@ -1992,52 +2024,63 @@ But don't look beyond what's visible on the screen."
 ;;*---------------------------------------------------------------------*/
 ;;*    flyspell-correct-word ...                                        */
 ;;*---------------------------------------------------------------------*/
+
 (defun flyspell-correct-word (event)
   "Pop up a menu of possible corrections for a misspelled word.
 The word checked is the word at the mouse position."
   (interactive "e")
-  ;; use the correct dictionary
-  (flyspell-accept-buffer-local-defs)
-  ;; retain cursor location (I don't know why but save-excursion here fails).
   (let ((save (point)))
     (mouse-set-point event)
-    (let ((cursor-location (point))
-         (word (flyspell-get-word nil)))
-      (if (consp word)
-         (let ((start (car (cdr word)))
-               (end (car (cdr (cdr word))))
-               (word (car word))
-               poss ispell-filter)
-           ;; now check spelling of word.
-           (ispell-send-string "%\n") ;put in verbose mode
-           (ispell-send-string (concat "^" word "\n"))
-           ;; wait until ispell has processed word
-            (while (progn
-                     (accept-process-output ispell-process)
-                     (not (string= "" (car ispell-filter)))))
-           ;; Remove leading empty element
-           (setq ispell-filter (cdr ispell-filter))
-           ;; ispell process should return something after word is sent.
-           ;; Tag word as valid (i.e., skip) otherwise
-           (or ispell-filter
-               (setq ispell-filter '(*)))
-           (if (consp ispell-filter)
-               (setq poss (ispell-parse-output (car ispell-filter))))
-           (cond
-            ((or (eq poss t) (stringp poss))
-             ;; don't correct word
-             t)
-            ((null poss)
-             ;; ispell error
-             (error "Ispell: error in Ispell process"))
-            ((featurep 'xemacs)
-             (flyspell-xemacs-popup
-               poss word cursor-location start end save))
-            (t
-             ;; The word is incorrect, we have to propose a replacement.
-              (flyspell-do-correct (flyspell-emacs-popup event poss word)
-                                   poss word cursor-location start end save)))
-           (ispell-pdict-save t))))))
+    (flyspell-correct-word-before-point event save)))
+
+(defun flyspell-correct-word-before-point (&optional event opoint)
+  "Pop up a menu of possible corrections for misspelled word before point.
+If EVENT is non-nil, it is the mouse event that invoked this operation;
+that controls where to put the menu.
+If OPOINT is non-nil, restore point there after adjusting it for replacement."
+  (interactive)
+  (unless (mouse-position)
+    (error "Pop-up menus do not work on this terminal"))
+  ;; use the correct dictionary
+  (flyspell-accept-buffer-local-defs)
+  (or opoint (setq opoint (point-marker)))
+  (let ((cursor-location (point))
+       (word (flyspell-get-word nil)))
+    (if (consp word)
+       (let ((start (car (cdr word)))
+             (end (car (cdr (cdr word))))
+             (word (car word))
+             poss ispell-filter)
+         ;; now check spelling of word.
+         (ispell-send-string "%\n")    ;put in verbose mode
+         (ispell-send-string (concat "^" word "\n"))
+         ;; wait until ispell has processed word
+         (while (progn
+                  (accept-process-output ispell-process)
+                  (not (string= "" (car ispell-filter)))))
+         ;; Remove leading empty element
+         (setq ispell-filter (cdr ispell-filter))
+         ;; ispell process should return something after word is sent.
+         ;; Tag word as valid (i.e., skip) otherwise
+         (or ispell-filter
+             (setq ispell-filter '(*)))
+         (if (consp ispell-filter)
+             (setq poss (ispell-parse-output (car ispell-filter))))
+         (cond
+          ((or (eq poss t) (stringp poss))
+           ;; don't correct word
+           t)
+          ((null poss)
+           ;; ispell error
+           (error "Ispell: error in Ispell process"))
+          ((featurep 'xemacs)
+           (flyspell-xemacs-popup
+            poss word cursor-location start end opoint))
+          (t
+           ;; The word is incorrect, we have to propose a replacement.
+           (flyspell-do-correct (flyspell-emacs-popup event poss word)
+                                poss word cursor-location start end opoint)))
+         (ispell-pdict-save t)))))
 
 ;;*---------------------------------------------------------------------*/
 ;;*    flyspell-do-correct ...                                      */
@@ -2109,6 +2152,8 @@ The word checked is the word at the mouse position."
 ;;*---------------------------------------------------------------------*/
 (defun flyspell-emacs-popup (event poss word)
   "The Emacs popup menu."
+  (unless window-system
+    (error "This command requires pop-up dialogs"))
   (if (not event)
       (let* ((mouse-pos  (mouse-position))
             (mouse-pos  (if (nth 1 mouse-pos)