Mercurial > emacs
changeset 30428:b7b96f09059d
Update to author's version 1.5d.
author | Gerd Moellmann <gerd@gnu.org> |
---|---|
date | Mon, 24 Jul 2000 18:36:17 +0000 |
parents | 42a7a2149c68 |
children | 08646b02c4ef |
files | lisp/textmodes/flyspell.el |
diffstat | 1 files changed, 1089 insertions(+), 331 deletions(-) [+] |
line wrap: on
line diff
--- a/lisp/textmodes/flyspell.el Mon Jul 24 18:30:41 2000 +0000 +++ b/lisp/textmodes/flyspell.el Mon Jul 24 18:36:17 2000 +0000 @@ -22,24 +22,23 @@ ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, ;; Boston, MA 02111-1307, USA. -;;; commentary: +;;; Commentary: ;; ;; Flyspell is a minor Emacs mode performing on-the-fly spelling ;; checking. -;; +;; ;; To enable Flyspell minor mode, type Meta-x flyspell-mode. ;; This applies only to the current buffer. +;; +;; To enable Flyspell in text representing computer programs, type +;; Meta-x flyspell-prog-mode. +;; In that mode only text inside comments are checked. ;; ;; Note: consider setting the variable ispell-parser to `tex' to -;; avoid TeX command checking; use `(setq ispell-parser 'tex)' -;; _before_ entering flyspell. +;; avoid TeX command checking; use `(setq ispell-parser 'tex)'. ;; ;; Some user variables control the behavior of flyspell. They are ;; those defined under the `User variables' comment. -;; -;; Note: as suggested by Yaron M. Minsky, if you use flyspell when -;; sending mails, you should add the following: -;; (add-hook 'mail-send-hook 'flyspell-mode-off) ;;; Code: (require 'ispell) @@ -51,11 +50,10 @@ "Spellchecking on the fly." :tag "FlySpell" :prefix "flyspell-" - :group 'processes - :version "20.3") + :group 'processes) ;*---------------------------------------------------------------------*/ -;* User variables ... */ +;* User configuration ... */ ;*---------------------------------------------------------------------*/ (defcustom flyspell-highlight-flag t "*How Flyspell should indicate misspelled words. @@ -68,12 +66,12 @@ :group 'flyspell :type 'boolean) -(defcustom flyspell-sort-corrections t +(defcustom flyspell-sort-corrections nil "*Non-nil means, sort the corrections alphabetically before popping them." :group 'flyspell :type 'boolean) -(defcustom flyspell-duplicate-distance 10000 +(defcustom flyspell-duplicate-distance -1 "*The maximum distance for finding duplicates of unrecognized words. This applies to the feature that when a word is not found in the dictionary, if the same spelling occurs elsewhere in the buffer, @@ -104,7 +102,9 @@ (defcustom flyspell-default-delayed-commands '(self-insert-command delete-backward-char - delete-char) + backward-or-forward-delete-char + delete-char + scrollbar-vertical-drag) "The standard list of delayed commands for Flyspell. See `flyspell-delayed-commands'." :group 'flyspell @@ -117,29 +117,95 @@ :group 'flyspell :type '(repeat (symbol))) +(defcustom flyspell-default-deplacement-commands + '(next-line + previous-line + scroll-up + scroll-down) + "The standard list of deplacement commands for Flyspell. +See `flyspell-deplacement-commands'." + :group 'flyspell + :type '(repeat (symbol))) + +(defcustom flyspell-deplacement-commands nil + "List of commands that are \"deplacement\" for Flyspell mode. +After these commands, Flyspell checking is performed only if the previous +command was not the very same command." + :group 'flyspell + :type '(repeat (symbol))) + (defcustom flyspell-issue-welcome-flag t "*Non-nil means that Flyspell should display a welcome message when started." :group 'flyspell :type 'boolean) -(defcustom flyspell-consider-dash-as-word-delimiter-flag nil - "*Non-nil means that the `-' char is considered as a word delimiter." +(defcustom flyspell-incorrect-hook nil + "*List of functions to be called when incorrect words are encountered. +Each function is given three arguments: the beginning and the end +of the incorrect region. The third is either the symbol 'doublon' or the list +of possible corrections returned as returned by 'ispell-parse-output'. + +If any of the functions return non-Nil, the word is not highligted as +incorrect." + :group 'flyspell + :type 'hook) + +(defcustom flyspell-default-dictionary "american" + "A string that is the name of the default dictionary. +This is passed to the ispell-change-dictionary when flyspell is started. +If the variables ispell-local-dictionary or ispell-dictionary are non nil +when flyspell is started, the value of that variables is used instead +of flyspell-default-dictionary to select the default dictionary." + :group 'flyspell + :type 'string) + +(defcustom flyspell-tex-command-regexp + "\\(\\(begin\\|end\\)[ \t]*{\\|\\(cite[a-z*]*\\|label\\|ref\\|eqref\\|usepackage\\|documentclass\\)[ \t]*\\(\\[[^]]*\\]\\)?{[^{}]*\\)" + "A string that is the regular expression that matches TeX commands." + :group 'flyspell + :type 'string) + +(defcustom flyspell-check-tex-math-command nil + "*Non nils means check even inside TeX math environement. TeX math +environement are discovered byt eh TEXMATHP that is implemented inside +the eponyme emacs package. That package may be found at: +http://strw.leidenuniv.nl/~dominik/Tools" :group 'flyspell :type 'boolean) -(defcustom flyspell-incorrect-hook nil - "*List of functions to be called when incorrect words are encountered. -Each function is given two arguments: the beginning and the end -of the incorrect region." - :group 'flyspell) +(defcustom flyspell-dictionaries-that-consider-dash-as-word-delimiter + '("francais" "deutsch8" "norsk") + "List of dictionary names that consider `-' as word delimiter." + :group 'flyspell + :type '(repeat (string))) -(defcustom flyspell-multi-language-p nil - "*Non-nil means that Flyspell can be used with multiple languages. -This mode works by starting a separate Ispell process for each buffer, -so that each buffer can use its own language." +(defcustom flyspell-abbrev-p + t + "*If true, add correction to abbreviation table." :group 'flyspell :type 'boolean) +(defcustom flyspell-use-global-abbrev-table-p + nil + "*If true, prefer global abbrev table to local abbrev table." + :group 'flyspell + :type 'boolean) + +;;;###autoload +(defcustom flyspell-mode-line-string " Fly" + "*String displayed on the modeline when flyspell is active. +Set this to nil if you don't want a modeline indicator." + :group 'flyspell + :type 'string) + +(defcustom flyspell-large-region 1000 + "*The threshold that determines if an region is small. The flyspell-region +is invoked, if the region is small, the word are checked one after the +other using regular flyspell check means. If the region is large, a new +ispell process is spawned to get speed." + :group 'flyspell + :type 'number) + ;*---------------------------------------------------------------------*/ ;* Mode specific options */ ;* ------------------------------------------------------------- */ @@ -158,20 +224,19 @@ property of the major mode name.") (make-variable-buffer-local 'flyspell-generic-check-word-p) +;*--- mail mode -------------------------------------------------------*/ (put 'mail-mode 'flyspell-mode-predicate 'mail-mode-flyspell-verify) (put 'message-mode 'flyspell-mode-predicate 'mail-mode-flyspell-verify) (defun mail-mode-flyspell-verify () "This function is used for `flyspell-generic-check-word-p' in Mail mode." (save-excursion - (or (progn - (beginning-of-line) - (looking-at "Subject:")) - (not (or (re-search-forward mail-header-separator nil t) - (re-search-backward message-signature-separator nil t) - (progn - (beginning-of-line) - (looking-at "[>}|]"))))))) + (not (or (re-search-forward mail-header-separator nil t) + (re-search-backward message-signature-separator nil t) + (progn + (beginning-of-line) + (looking-at "[>}|]\\To:")))))) +;*--- texinfo mode ----------------------------------------------------*/ (put 'texinfo-mode 'flyspell-mode-predicate 'texinfo-mode-flyspell-verify) (defun texinfo-mode-flyspell-verify () "This function is used for `flyspell-generic-check-word-p' in Texinfo mode." @@ -179,18 +244,74 @@ (forward-word -1) (not (looking-at "@")))) +;*--- tex mode --------------------------------------------------------*/ +(put 'tex-mode 'flyspell-mode-predicate 'tex-mode-flyspell-verify) +(defun tex-mode-flyspell-verify () + "This function is used for `flyspell-generic-check-word-p' in LaTeX mode." + (and + (not (save-excursion + (re-search-backward "^[ \t]*%%%[ \t]+Local" (point-min) t))) + (not (save-excursion + (let ((this (point-marker)) + (e (progn (end-of-line) (point-marker)))) + (beginning-of-line) + (if (re-search-forward "\\\\\\(cite\\|label\\|ref\\){[^}]*}" e t) + (and (>= this (match-beginning 0)) + (<= this (match-end 0)) ))))))) + +;*--- sgml mode -------------------------------------------------------*/ +(put 'sgml-mode 'flyspell-mode-predicate 'sgml-mode-flyspell-verify) +(put 'html-mode 'flyspell-mode-predicate 'sgml-mode-flyspell-verify) + +(defun sgml-mode-flyspell-verify () + "This function is used for `flyspell-generic-check-word-p' in SGML mode." + (not (save-excursion + (let ((this (point-marker)) + (s (progn (beginning-of-line) (point-marker))) + (e (progn (end-of-line) (point-marker)))) + (or (progn + (goto-char this) + (and (re-search-forward "[^<]*>" e t) + (= (match-beginning 0) this))) + (progn + (goto-char this) + (and (re-search-backward "<[^>]*" s t) + (= (match-end 0) this))) + (and (progn + (goto-char this) + (and (re-search-forward "[^&]*;" e t) + (= (match-beginning 0) this))) + (progn + (goto-char this) + (and (re-search-backward "&[^;]*" s t) + (= (match-end 0) this))))))))) + +;*---------------------------------------------------------------------*/ +;* Programming mode */ +;*---------------------------------------------------------------------*/ +(defun flyspell-generic-progmode-verify () + "Used for `flyspell-generic-check-word-p' in programming modes." + (let ((f (get-text-property (point) 'face))) + (memq f '(font-lock-comment-face font-lock-string-face)))) + +;;;###autoload +(defun flyspell-prog-mode () + "Turn on `flyspell-mode' for comments and strings." + (interactive) + (setq flyspell-generic-check-word-p 'flyspell-generic-progmode-verify) + (flyspell-mode 1)) + ;*---------------------------------------------------------------------*/ ;* Overlay compatibility */ ;*---------------------------------------------------------------------*/ -(autoload 'make-overlay "overlay" "" t) -(autoload 'move-overlay "overlay" "" t) -(autoload 'overlayp "overlay" "" t) -(autoload 'overlay-properties "overlay" "" t) -(autoload 'overlays-in "overlay" "" t) -(autoload 'delete-overlay "overlay" "" t) -(autoload 'overlays-at "overlay" "" t) -(autoload 'overlay-put "overlay" "" t) -(autoload 'overlay-get "overlay" "" t) +(autoload 'make-overlay "overlay" "Overlay compatibility kit." t) +(autoload 'overlayp "overlay" "Overlay compatibility kit." t) +(autoload 'overlays-in "overlay" "Overlay compatibility kit." t) +(autoload 'delete-overlay "overlay" "Overlay compatibility kit." t) +(autoload 'overlays-at "overlay" "Overlay compatibility kit." t) +(autoload 'overlay-put "overlay" "Overlay compatibility kit." t) +(autoload 'overlay-get "overlay" "Overlay compatibility kit." t) +(autoload 'previous-overlay-change "overlay" "Overlay compatibility kit." t) ;*---------------------------------------------------------------------*/ ;* Which emacs are we currently running */ @@ -218,33 +339,41 @@ (cond ((eq flyspell-emacs 'xemacs) (define-key map [(button2)] - #'flyspell-correct-word/mouse-keymap) + #'flyspell-correct-word/mouse-keymap) (define-key flyspell-mouse-map "\M-\t" #'flyspell-auto-correct-word)) (flyspell-use-local-map (define-key map [(mouse-2)] #'flyspell-correct-word/mouse-keymap) (define-key map "\M-\t" #'flyspell-auto-correct-word))) map)) + +;;;###autoload (defvar flyspell-mode-map (make-sparse-keymap)) -(or (assoc 'flyspell-mode minor-mode-alist) - (setq minor-mode-alist - (cons '(flyspell-mode " Fly") minor-mode-alist))) - -;; mouse or local-map bindings +;; mouse, keyboard bindings and misc definition (when (or (assoc 'flyspell-mode minor-mode-map-alist) - (setq minor-mode-map-alist - (cons (cons 'flyspell-mode flyspell-mode-map) - minor-mode-map-alist))) - (define-key flyspell-mode-map "\M-\t" 'flyspell-auto-correct-word) - (define-key flyspell-mode-map [(mouse-2)] - (function flyspell-correct-word/local-keymap))) + (setq minor-mode-map-alist + (cons (cons 'flyspell-mode flyspell-mode-map) + minor-mode-map-alist))) + (define-key flyspell-mode-map "\M-\t" 'flyspell-auto-correct-word) + (define-key flyspell-mode-map [(mouse-2)] + (function flyspell-correct-word/local-keymap))) + ;; the name of the overlay property that defines the keymap (defvar flyspell-overlay-keymap-property-name (if (string-match "19.*XEmacs" emacs-version) 'keymap 'local-map)) - + +;; dash character machinery +(defvar flyspell-consider-dash-as-word-delimiter-flag nil + "*Non-nil means that the `-' char is considered as a word delimiter.") +(make-variable-buffer-local 'flyspell-consider-dash-as-word-delimiter-flag) +(defvar flyspell-dash-dictionary nil) +(make-variable-buffer-local 'flyspell-dash-dictionary) +(defvar flyspell-dash-local-dictionary nil) +(make-variable-buffer-local 'flyspell-dash-local-dictionary) + ;*---------------------------------------------------------------------*/ ;* Highlighting */ ;*---------------------------------------------------------------------*/ @@ -309,6 +438,58 @@ (set-buffer-modified-p (buffer-modified-p))))) ;*---------------------------------------------------------------------*/ +;* Autoloading */ +;*---------------------------------------------------------------------*/ +;;;###autoload +(if (fboundp 'add-minor-mode) + (add-minor-mode 'flyspell-mode + 'flyspell-mode-line-string + flyspell-mode-map + nil + 'flyspell-mode) + (or (assoc 'flyspell-mode minor-mode-alist) + (setq minor-mode-alist + (cons '(flyspell-mode flyspell-mode-line-string) + minor-mode-alist))) + + (or (assoc 'flyspell-mode minor-mode-map-alist) + (setq minor-mode-map-alist + (cons (cons 'flyspell-mode flyspell-mode-map) + minor-mode-map-alist)))) + + +;*---------------------------------------------------------------------*/ +;* flyspell-buffers ... */ +;* ------------------------------------------------------------- */ +;* For remembering buffers running flyspell */ +;*---------------------------------------------------------------------*/ +(defvar flyspell-buffers nil) + +;*---------------------------------------------------------------------*/ +;* flyspell-minibuffer-p ... */ +;*---------------------------------------------------------------------*/ +(defun flyspell-minibuffer-p (buffer) + "Is BUFFER a minibuffer?" + (let ((ws (get-buffer-window-list buffer t))) + (and (consp ws) (window-minibuffer-p (car ws))))) + +;*---------------------------------------------------------------------*/ +;* flyspell-accept-buffer-local-defs ... */ +;*---------------------------------------------------------------------*/ +(defun flyspell-accept-buffer-local-defs () + (ispell-accept-buffer-local-defs) + (if (not (and (eq flyspell-dash-dictionary ispell-dictionary) + (eq flyspell-dash-local-dictionary ispell-local-dictionary))) + ;; the dictionary as changed + (progn + (setq flyspell-dash-dictionary ispell-dictionary) + (setq flyspell-dash-local-dictionary ispell-local-dictionary) + (if (member (or ispell-local-dictionary ispell-dictionary) + flyspell-dictionaries-that-consider-dash-as-word-delimiter) + (setq flyspell-consider-dash-as-word-delimiter-flag t) + (setq flyspell-consider-dash-as-word-delimiter-flag nil))))) + +;*---------------------------------------------------------------------*/ ;* flyspell-mode-on ... */ ;*---------------------------------------------------------------------*/ (eval-when-compile (defvar flyspell-local-mouse-map)) @@ -316,53 +497,47 @@ (defun flyspell-mode-on () "Turn Flyspell mode on. Do not use this; use `flyspell-mode' instead." (setq ispell-highlight-face 'flyspell-incorrect-face) - ;; ispell initialization - (if flyspell-multi-language-p - (progn - (make-variable-buffer-local 'ispell-dictionary) - (make-variable-buffer-local 'ispell-process) - (make-variable-buffer-local 'ispell-filter) - (make-variable-buffer-local 'ispell-filter-continue) - (make-variable-buffer-local 'ispell-process-directory) - (make-variable-buffer-local 'ispell-parser) - (put 'ispell-dictionary 'permanent-local t) - (put 'ispell-process 'permanent-local t) - (put 'ispell-filter 'permanent-local t) - (put 'ispell-filter-continue 'permanent-local t) - (put 'ispell-process-directory 'permanent-local t) - (put 'ispell-parser 'permanent-local t))) - ;; We put the `flyspell-delayed' property on some commands. + ;; local dictionaries setup + (ispell-change-dictionary + (or ispell-local-dictionary ispell-dictionary flyspell-default-dictionary)) + ;; we have to force ispell to accept the local definition or + ;; otherwise it could be too late, the local dictionary may + ;; be forgotten! + (flyspell-accept-buffer-local-defs) + ;; we put the `flyspel-delayed' property on some commands (flyspell-delay-commands) + ;; we put the `flyspel-deplacement' property on some commands + (flyspell-deplacement-commands) ;; we bound flyspell action to post-command hook (make-local-hook 'post-command-hook) (add-hook 'post-command-hook (function flyspell-post-command-hook) t t) ;; we bound flyspell action to pre-command hook (make-local-hook 'pre-command-hook) (add-hook 'pre-command-hook (function flyspell-pre-command-hook) t t) - - ;; Set flyspell-generic-check-word-p based on the major mode. + ;; we bound flyspell action to after-change hook + (make-local-variable 'after-change-functions) + (setq after-change-functions + (cons 'flyspell-after-change-function after-change-functions)) + ;; set flyspell-generic-check-word-p based on the major mode (let ((mode-predicate (get major-mode 'flyspell-mode-predicate))) (if mode-predicate (setq flyspell-generic-check-word-p mode-predicate))) - + ;; work around the fact that the `local-map' text-property replaces the + ;; buffer's local map rather than shadowing it. + (set (make-local-variable 'flyspell-mouse-map) + (let ((map (copy-keymap flyspell-mouse-map))) + (set-keymap-parent map (current-local-map)) + map)) ;; the welcome message (if flyspell-issue-welcome-flag (let ((binding (where-is-internal 'flyspell-auto-correct-word nil 'non-ascii))) (message (if binding - (format "Welcome to flyspell. Use %s or Mouse-2 to correct words." + (format "Welcome to flyspell. Use %s or Mouse-2 to correct words." (key-description binding)) - "Welcome to flyspell. Use Mouse-2 to correct words.")))) - ;; we have to kill the flyspell process when the buffer is deleted. - ;; (thanks to Jeff Miller and Roland Rosenfeld who sent me this - ;; improvement). - (add-hook 'kill-buffer-hook - (lambda () - (if (and flyspell-multi-language-p ispell-process) - (ispell-kill-ispell t)))) - (make-local-hook 'change-major-mode-hook) - (add-hook 'change-major-mode-hook 'flyspell-mode-off) + "Welcome to flyspell. Use Mouse-2 to correct words.")))) + ;; Use this so that we can still get major mode bindings at a ;; misspelled word (unless they're overridden by ;; `flyspell-mouse-map'). @@ -372,6 +547,7 @@ (set-keymap-parents (list (current-local-map))) (set-keymap-parent map (current-local-map))) map)) + ;; we end with the flyspell hooks (run-hooks 'flyspell-mode-hook)) @@ -395,22 +571,23 @@ (put command 'flyspell-delayed t)) ;*---------------------------------------------------------------------*/ -;* flyspell-ignore-commands ... */ +;* flyspell-deplacement-commands ... */ ;*---------------------------------------------------------------------*/ -(defun flyspell-ignore-commands () - "This is an obsolete function, use `flyspell-delay-commands' instead." - (flyspell-delay-commands)) +(defun flyspell-deplacement-commands () + "Install the standard set of Flyspell deplacement commands." + (mapcar 'flyspell-deplacement-command flyspell-default-deplacement-commands) + (mapcar 'flyspell-deplacement-command flyspell-deplacement-commands)) ;*---------------------------------------------------------------------*/ -;* flyspell-ignore-command ... */ +;* flyspell-deplacement-command ... */ ;*---------------------------------------------------------------------*/ -(defun flyspell-ignore-command (command) - "This is an obsolete function, use `flyspell-delay-command' instead. -COMMAND is the name of the command to be delayed." - (flyspell-delay-command command)) - -(make-obsolete 'flyspell-ignore-commands 'flyspell-delay-commands) -(make-obsolete 'flyspell-ignore-command 'flyspell-delay-command) +(defun flyspell-deplacement-command (command) + "Set COMMAND that implement cursor movements, for Flyspell. +When flyspell `post-command-hook' is invoked because of a deplacement command +as been used the current word is checked only if the previous command was +not the very same deplacement command." + (interactive "SDeplacement Flyspell after Command: ") + (put command 'flyspell-deplacement t)) ;*---------------------------------------------------------------------*/ ;* flyspell-word-cache ... */ @@ -427,8 +604,17 @@ ;* post command hook, we will check, if the word at this position */ ;* has to be spell checked. */ ;*---------------------------------------------------------------------*/ -(defvar flyspell-pre-buffer nil) -(defvar flyspell-pre-point nil) +(defvar flyspell-pre-buffer nil) +(defvar flyspell-pre-point nil) +(defvar flyspell-pre-column nil) +(defvar flyspell-pre-pre-buffer nil) +(defvar flyspell-pre-pre-point nil) + +;*---------------------------------------------------------------------*/ +;* flyspell-previous-command ... */ +;*---------------------------------------------------------------------*/ +(defvar flyspell-previous-command nil + "The last interactive command checked by Flyspell.") ;*---------------------------------------------------------------------*/ ;* flyspell-pre-command-hook ... */ @@ -437,7 +623,8 @@ "Save the current buffer and point for Flyspell's post-command hook." (interactive) (setq flyspell-pre-buffer (current-buffer)) - (setq flyspell-pre-point (point))) + (setq flyspell-pre-point (point)) + (setq flyspell-pre-column (current-column))) ;*---------------------------------------------------------------------*/ ;* flyspell-mode-off ... */ @@ -445,13 +632,11 @@ ;;;###autoload (defun flyspell-mode-off () "Turn Flyspell mode off." - ;; If we have an Ispell process for each buffer, - ;; kill the one for this buffer. - (if flyspell-multi-language-p - (ispell-kill-ispell t)) ;; we remove the hooks (remove-hook 'post-command-hook (function flyspell-post-command-hook) t) (remove-hook 'pre-command-hook (function flyspell-pre-command-hook) t) + (setq after-change-functions (delq 'flyspell-after-change-function + after-change-functions)) ;; we remove all the flyspell hilightings (flyspell-delete-all-overlays) ;; we have to erase pre cache variables @@ -461,40 +646,6 @@ (setq flyspell-mode nil)) ;*---------------------------------------------------------------------*/ -;* flyspell-check-word-p ... */ -;*---------------------------------------------------------------------*/ -(defun flyspell-check-word-p () - "Return t when the word at `point' has to be checked. -The answer depends of several criteria. -Mostly we check word delimiters." - (cond - ((<= (- (point-max) 1) (point-min)) - ;; the buffer is not filled enough - nil) - ((not (and (symbolp this-command) (get this-command 'flyspell-delayed))) - ;; the current command is not delayed, that - ;; is that we must check the word now - t) - ((and (> (point) (point-min)) - (save-excursion - (backward-char 1) - (and (looking-at (flyspell-get-not-casechars)) - (or flyspell-consider-dash-as-word-delimiter-flag - (not (looking-at "\\-")))))) - ;; yes because we have reached or typed a word delimiter. - t) - ((not (integerp flyspell-delay)) - ;; yes because the user had set up a no-delay configuration. - t) - (executing-kbd-macro - ;; Don't delay inside a keyboard macro. - t) - (t - (if (fboundp 'about-xemacs) - (sit-for flyspell-delay nil) - (sit-for flyspell-delay 0 nil))))) - -;*---------------------------------------------------------------------*/ ;* flyspell-check-pre-word-p ... */ ;*---------------------------------------------------------------------*/ (defun flyspell-check-pre-word-p () @@ -506,11 +657,22 @@ (not (bufferp flyspell-pre-buffer)) (not (buffer-live-p flyspell-pre-buffer))) nil) + ((and (eq flyspell-pre-pre-point flyspell-pre-point) + (eq flyspell-pre-pre-buffer flyspell-pre-buffer)) + nil) ((or (and (= flyspell-pre-point (- (point) 1)) (eq (char-syntax (char-after flyspell-pre-point)) ?w)) (= flyspell-pre-point (point)) (= flyspell-pre-point (+ (point) 1))) nil) + ((and (symbolp this-command) + (or (get this-command 'flyspell-delayed) + (and (get this-command 'flyspell-deplacement) + (eq flyspell-previous-command this-command))) + (or (= (current-column) 0) + (= (current-column) flyspell-pre-column) + (eq (char-syntax (char-after flyspell-pre-point)) ?w))) + nil) ((not (eq (current-buffer) flyspell-pre-buffer)) t) ((not (and (numberp flyspell-word-cache-start) @@ -519,21 +681,241 @@ (t (or (< flyspell-pre-point flyspell-word-cache-start) (> flyspell-pre-point flyspell-word-cache-end))))) - + +;*---------------------------------------------------------------------*/ +;* The flyspell after-change-hook, store the change position. In */ +;* the post command hook, we will check, if the word at this */ +;* position has to be spell checked. */ +;*---------------------------------------------------------------------*/ +(defvar flyspell-changes nil) + +;*---------------------------------------------------------------------*/ +;* flyspell-after-change-function ... */ +;*---------------------------------------------------------------------*/ +(defun flyspell-after-change-function (start stop len) + "Save the current buffer and point for Flyspell's post-command hook." + (interactive) + (setq flyspell-changes (cons (cons start stop) flyspell-changes))) + +;*---------------------------------------------------------------------*/ +;* flyspell-check-changed-word-p ... */ +;*---------------------------------------------------------------------*/ +(defun flyspell-check-changed-word-p (start stop) + "Return t when the changed word has to be checked. +The answer depends of several criteria. +Mostly we check word delimiters." + (cond + ((and (eq (char-after start) ?\n) (> stop start)) + t) + ((not (numberp flyspell-pre-point)) + t) + ((and (>= flyspell-pre-point start) (<= flyspell-pre-point stop)) + nil) + ((let ((pos (point))) + (or (>= pos start) (<= pos stop) (= pos (1+ stop)))) + nil) + (t + t))) + +;*---------------------------------------------------------------------*/ +;* flyspell-check-word-p ... */ +;*---------------------------------------------------------------------*/ +(defun flyspell-check-word-p () + "Return t when the word at `point' has to be checked. +The answer depends of several criteria. +Mostly we check word delimiters." + (cond + ((<= (- (point-max) 1) (point-min)) + ;; the buffer is not filled enough + nil) + ((and (and (> (current-column) 0) + (not (eq (current-column) flyspell-pre-column))) + (save-excursion + (backward-char 1) + (and (looking-at (flyspell-get-not-casechars)) + (or flyspell-consider-dash-as-word-delimiter-flag + (not (looking-at "\\-")))))) + ;; yes because we have reached or typed a word delimiter. + t) + ((symbolp this-command) + (cond + ((get this-command 'flyspell-deplacement) + (not (eq flyspell-previous-command this-command))) + ((get this-command 'flyspell-delayed) + ;; the current command is not delayed, that + ;; is that we must check the word now + (if (fboundp 'about-xemacs) + (sit-for flyspell-delay nil) + (sit-for flyspell-delay 0 nil))) + (t t))) + (t t))) + +;*---------------------------------------------------------------------*/ +;* flyspell-debug-signal-no-check ... */ +;*---------------------------------------------------------------------*/ +(defun flyspell-debug-signal-no-check (msg obj) + (setq debug-on-error t) + (save-excursion + (let ((buffer (get-buffer-create "*flyspell-debug*"))) + (set-buffer buffer) + (erase-buffer) + (insert "NO-CHECK:\n") + (insert (format " %S : %S\n" msg obj))))) + +;*---------------------------------------------------------------------*/ +;* flyspell-debug-signal-pre-word-checked ... */ +;*---------------------------------------------------------------------*/ +(defun flyspell-debug-signal-pre-word-checked () + (setq debug-on-error t) + (save-excursion + (let ((buffer (get-buffer-create "*flyspell-debug*"))) + (set-buffer buffer) + (insert "PRE-WORD:\n") + (insert (format " pre-point : %S\n" flyspell-pre-point)) + (insert (format " pre-buffer : %S\n" flyspell-pre-buffer)) + (insert (format " cache-start: %S\n" flyspell-word-cache-start)) + (insert (format " cache-end : %S\n" flyspell-word-cache-end)) + (goto-char (point-max))))) + +;*---------------------------------------------------------------------*/ +;* flyspell-debug-signal-word-checked ... */ +;*---------------------------------------------------------------------*/ +(defun flyspell-debug-signal-word-checked () + (setq debug-on-error t) + (save-excursion + (let ((oldbuf (current-buffer)) + (buffer (get-buffer-create "*flyspell-debug*")) + (point (point))) + (set-buffer buffer) + (insert "WORD:\n") + (insert (format " this-cmd : %S\n" this-command)) + (insert (format " delayed : %S\n" (and (symbolp this-command) + (get this-command 'flyspell-delayed)))) + (insert (format " point : %S\n" point)) + (insert (format " prev-char : [%c] %S\n" + (progn + (set-buffer oldbuf) + (let ((c (if (> (point) (point-min)) + (save-excursion + (backward-char 1) + (char-after (point))) + ? ))) + (set-buffer buffer) + c)) + (progn + (set-buffer oldbuf) + (let ((c (if (> (point) (point-min)) + (save-excursion + (backward-char 1) + (and (and (looking-at (flyspell-get-not-casechars)) 1) + (and (or flyspell-consider-dash-as-word-delimiter-flag + (not (looking-at "\\-"))) 2)))))) + (set-buffer buffer) + c)))) + (insert (format " because : %S\n" + (cond + ((not (and (symbolp this-command) + (get this-command 'flyspell-delayed))) + ;; the current command is not delayed, that + ;; is that we must check the word now + 'not-delayed) + ((progn + (set-buffer oldbuf) + (let ((c (if (> (point) (point-min)) + (save-excursion + (backward-char 1) + (and (looking-at (flyspell-get-not-casechars)) + (or flyspell-consider-dash-as-word-delimiter-flag + (not (looking-at "\\-")))))))) + (set-buffer buffer) + c)) + ;; yes because we have reached or typed a word delimiter. + 'separator) + ((not (integerp flyspell-delay)) + ;; yes because the user had set up a no-delay configuration. + 'no-delay) + (t + 'sit-for)))) + (goto-char (point-max))))) + +;*---------------------------------------------------------------------*/ +;* flyspell-debug-signal-changed-checked ... */ +;*---------------------------------------------------------------------*/ +(defun flyspell-debug-signal-changed-checked () + (setq debug-on-error t) + (save-excursion + (let ((buffer (get-buffer-create "*flyspell-debug*")) + (point (point))) + (set-buffer buffer) + (insert "CHANGED WORD:\n") + (insert (format " point : %S\n" point)) + (goto-char (point-max))))) + ;*---------------------------------------------------------------------*/ ;* flyspell-post-command-hook ... */ +;* ------------------------------------------------------------- */ +;* It is possible that we check several words: */ +;* 1- the current word is checked if the predicate */ +;* FLYSPELL-CHECK-WORD-P is true */ +;* 2- the word that used to be the current word before the */ +;* THIS-COMMAND is checked if: */ +;* a- the previous word is different from the current word */ +;* b- the previous word as not just been checked by the */ +;* previous FLYSPELL-POST-COMMAND-HOOK */ +;* 3- the words changed by the THIS-COMMAND that are neither the */ +;* previous word nor the current word */ ;*---------------------------------------------------------------------*/ (defun flyspell-post-command-hook () "The `post-command-hook' used by flyspell to check a word in-the-fly." (interactive) - (if (flyspell-check-word-p) - (flyspell-word)) - (if (flyspell-check-pre-word-p) - (save-excursion - (set-buffer flyspell-pre-buffer) + (let ((command this-command)) + (if (flyspell-check-pre-word-p) (save-excursion - (goto-char flyspell-pre-point) - (flyspell-word))))) + '(flyspell-debug-signal-pre-word-checked) + (set-buffer flyspell-pre-buffer) + (save-excursion + (goto-char flyspell-pre-point) + (flyspell-word)))) + (if (flyspell-check-word-p) + (progn + '(flyspell-debug-signal-word-checked) + (flyspell-word) + ;; we remember which word we have just checked. + ;; this will be used next time we will check a word + ;; to compare the next current word with the word + ;; that as been registered in the pre-command-hook + ;; that is these variables are used within the predicate + ;; FLYSPELL-CHECK-PRE-WORD-P + (setq flyspell-pre-pre-buffer (current-buffer)) + (setq flyspell-pre-pre-point (point))) + (progn + (setq flyspell-pre-pre-buffer nil) + (setq flyspell-pre-pre-point nil) + ;; when a word is not checked because of a delayed command + ;; we do not disable the ispell cache. + (if (and (symbolp this-command) (get this-command 'flyspell-delayed)) + (setq flyspell-word-cache-end -1)))) + (while (consp flyspell-changes) + (let ((start (car (car flyspell-changes))) + (stop (cdr (car flyspell-changes)))) + (if (flyspell-check-changed-word-p start stop) + (save-excursion + '(flyspell-debug-signal-changed-checked) + (goto-char start) + (flyspell-word))) + (setq flyspell-changes (cdr flyspell-changes)))) + (setq flyspell-previous-command command))) + +;*---------------------------------------------------------------------*/ +;* flyspell-notify-misspell ... */ +;*---------------------------------------------------------------------*/ +(defun flyspell-notify-misspell (start end word poss) + (let ((replacements (if (stringp poss) + poss + (if flyspell-sort-corrections + (sort (car (cdr (cdr poss))) 'string<) + (car (cdr (cdr poss))))))) + (message (format "mispelling `%s' %S" word replacements)))) ;*---------------------------------------------------------------------*/ ;* flyspell-word ... */ @@ -544,22 +926,25 @@ (if (interactive-p) (setq following ispell-following-word)) (save-excursion - (ispell-accept-buffer-local-defs) ; use the correct dictionary - (let ((cursor-location (point)) ; retain cursor location - (word (flyspell-get-word following)) - start end poss) - (if (or (eq word nil) + ;; use the correct dictionary + (flyspell-accept-buffer-local-defs) + (let* ((cursor-location (point)) + (flyspell-word (flyspell-get-word following)) + start end poss word) + (if (or (eq flyspell-word nil) (and (fboundp flyspell-generic-check-word-p) (not (funcall flyspell-generic-check-word-p)))) - t + '() (progn - ;; destructure return word info list. - (setq start (car (cdr word)) - end (car (cdr (cdr word))) - word (car word)) + ;; destructure return flyspell-word info list. + (setq start (car (cdr flyspell-word)) + end (car (cdr (cdr flyspell-word))) + word (car flyspell-word)) ;; before checking in the directory, we check for doublons. (cond - ((and flyspell-mark-duplications-flag + ((and (or (not (eq ispell-parser 'tex)) + (not (eq (char-after start) ?\\))) + flyspell-mark-duplications-flag (save-excursion (goto-char start) (word-search-backward word @@ -567,14 +952,14 @@ (+ 1 (- end start))) t))) ;; yes, this is a doublon - (flyspell-highlight-incorrect-region start end)) + (flyspell-highlight-incorrect-region start end 'doublon)) ((and (eq flyspell-word-cache-start start) (eq flyspell-word-cache-end end) (string-equal flyspell-word-cache-word word)) ;; this word had been already checked, we skip nil) ((and (eq ispell-parser 'tex) - (flyspell-tex-command-p word)) + (flyspell-tex-command-p flyspell-word)) ;; this is a correct word (because a tex command) (flyspell-unhighlight-at start) (if (> end start) @@ -601,7 +986,7 @@ ;; (process-send-string ispell-process "!\n") ;; back to terse mode. (setq ispell-filter (cdr ispell-filter)) - (if (listp ispell-filter) + (if (consp ispell-filter) (setq poss (ispell-parse-output (car ispell-filter)))) (cond ((eq poss t) ;; correct @@ -618,8 +1003,7 @@ ((null poss) (flyspell-unhighlight-at start) (if (> end start) - (flyspell-unhighlight-at (- end 1))) - (message "Error in ispell process")) + (flyspell-unhighlight-at (- end 1)))) ((or (and (< flyspell-duplicate-distance 0) (or (save-excursion (goto-char start) @@ -652,17 +1036,55 @@ (t ;; incorrect highlight the location (if flyspell-highlight-flag - (flyspell-highlight-incorrect-region start end) - (message (format "mispelling `%s'" word))))) - (goto-char cursor-location) ; return to original location + (flyspell-highlight-incorrect-region start end poss) + (flyspell-notify-misspell start end word poss)))) + ;; return to original location + (goto-char cursor-location) (if ispell-quit (setq ispell-quit nil))))))))) ;*---------------------------------------------------------------------*/ +;* flyspell-tex-math-initialized ... */ +;*---------------------------------------------------------------------*/ +(defvar flyspell-tex-math-initialized nil) + +;*---------------------------------------------------------------------*/ +;* flyspell-math-tex-command-p ... */ +;* ------------------------------------------------------------- */ +;* This function uses the texmathp package to check if (point) */ +;* is within a tex command. In order to avoid using */ +;* condition-case each time we use the variable */ +;* flyspell-tex-math-initialized to make a special case the first */ +;* time that function is called. */ +;*---------------------------------------------------------------------*/ +(defun flyspell-math-tex-command-p () + (cond + (flyspell-check-tex-math-command + nil) + ((eq flyspell-tex-math-initialized t) + (texmathp)) + ((eq flyspell-tex-math-initialized 'error) + nil) + (t + (setq flyspell-tex-math-initialized t) + (condition-case nil + (texmathp) + (error (progn + (setq flyspell-tex-math-initialized 'error) + nil)))))) + +;*---------------------------------------------------------------------*/ ;* flyspell-tex-command-p ... */ ;*---------------------------------------------------------------------*/ (defun flyspell-tex-command-p (word) "Return t if WORD is a TeX command." - (eq (aref word 0) ?\\)) + (or (save-excursion + (let ((b (car (cdr word)))) + (and (re-search-backward "\\\\" (- (point) 100) t) + (or (= (match-end 0) b) + (and (goto-char (match-end 0)) + (looking-at flyspell-tex-command-regexp) + (>= (match-end 0) b)))))) + (flyspell-math-tex-command-p))) ;*---------------------------------------------------------------------*/ ;* flyspell-casechars-cache ... */ @@ -681,15 +1103,13 @@ this function changes the last char of the `ispell-casechars' string." (let ((ispell-casechars (ispell-get-casechars))) (cond - ((eq ispell-casechars flyspell-ispell-casechars-cache) - flyspell-casechars-cache) - ((not (eq ispell-parser 'tex)) + ((eq ispell-parser 'tex) (setq flyspell-ispell-casechars-cache ispell-casechars) (setq flyspell-casechars-cache (concat (substring ispell-casechars 0 (- (length ispell-casechars) 1)) - "{}]")) + "]")) flyspell-casechars-cache) (t (setq flyspell-ispell-casechars-cache ispell-casechars) @@ -711,15 +1131,13 @@ "This function builds a string that is the regexp of non-word chars." (let ((ispell-not-casechars (ispell-get-not-casechars))) (cond - ((eq ispell-not-casechars flyspell-ispell-not-casechars-cache) - flyspell-not-casechars-cache) - ((not (eq ispell-parser 'tex)) + ((eq ispell-parser 'tex) (setq flyspell-ispell-not-casechars-cache ispell-not-casechars) (setq flyspell-not-casechars-cache (concat (substring ispell-not-casechars 0 (- (length ispell-not-casechars) 1)) - "{}]")) + "]")) flyspell-not-casechars-cache) (t (setq flyspell-ispell-not-casechars-cache ispell-not-casechars) @@ -731,7 +1149,7 @@ ;*---------------------------------------------------------------------*/ (defun flyspell-get-word (following) "Return the word for spell-checking according to Ispell syntax. -If optional argument FOLLOWING is non-nil or if `ispell-following-word' +If argument FOLLOWING is non-nil or if `ispell-following-word' is non-nil when called interactively, then the following word \(rather than preceding\) is checked when the cursor is not over a word. Optional second argument contains otherchars that can be included in word @@ -742,35 +1160,27 @@ (flyspell-not-casechars (flyspell-get-not-casechars)) (ispell-otherchars (ispell-get-otherchars)) (ispell-many-otherchars-p (ispell-get-many-otherchars-p)) - (word-regexp (if (not (string= "" ispell-otherchars)) - (concat - flyspell-casechars - "+\\(" - ispell-otherchars - "?" - flyspell-casechars - "+\\)" - (if ispell-many-otherchars-p - "*" "?")) + (word-regexp (if (string< "" ispell-otherchars) + (concat flyspell-casechars + "+\\(" + ispell-otherchars + "?" + flyspell-casechars + "+\\)" + (if ispell-many-otherchars-p + "*" "?")) (concat flyspell-casechars "+"))) - (tex-prelude "[\\\\{]") - (tex-regexp (if (eq ispell-parser 'tex) - (concat tex-prelude "?" word-regexp "}?") - word-regexp)) - did-it-once start end word) ;; find the word - (if (not (or (looking-at flyspell-casechars) - (and (eq ispell-parser 'tex) - (looking-at tex-prelude)))) + (if (not (looking-at flyspell-casechars)) (if following (re-search-forward flyspell-casechars (point-max) t) (re-search-backward flyspell-casechars (point-min) t))) ;; move to front of word (re-search-backward flyspell-not-casechars (point-min) 'start) - (if (not (string= "" ispell-otherchars)) - (let ((pos nil)) + (let ((pos nil)) + (if (string< "" ispell-otherchars) (while (and (looking-at ispell-otherchars) (not (bobp)) (or (not did-it-once) @@ -783,7 +1193,7 @@ (re-search-backward flyspell-not-casechars (point-min) 'move) (backward-char -1))))) ;; Now mark the word and save to string. - (if (eq (re-search-forward tex-regexp (point-max) t) nil) + (if (eq (re-search-forward word-regexp (point-max) t) nil) nil (progn (setq start (match-beginning 0) @@ -792,16 +1202,15 @@ (list word start end))))) ;*---------------------------------------------------------------------*/ -;* flyspell-region ... */ +;* flyspell-small-region ... */ ;*---------------------------------------------------------------------*/ -(defun flyspell-region (beg end) +(defun flyspell-small-region (beg end) "Flyspell text between BEG and END." - (interactive "r") (save-excursion - (if (> beg end) - (let ((old beg)) - (setq beg end) - (setq end old))) + (if (> beg end) + (let ((old beg)) + (setq beg end) + (setq end old))) (goto-char beg) (let ((count 0)) (while (< (point) end) @@ -812,15 +1221,130 @@ (setq count 0)) (setq count (+ 1 count))) (flyspell-word) + (sit-for 0) (let ((cur (point))) (forward-word 1) (if (and (< (point) end) (> (point) (+ cur 1))) (backward-char 1))))) (backward-char 1) - (message "Spell Checking...done") + (message "Spell Checking completed.") (flyspell-word))) ;*---------------------------------------------------------------------*/ +;* flyspell-external-ispell-process ... */ +;*---------------------------------------------------------------------*/ +(defvar flyspell-external-ispell-process '() + "The external Flyspell ispell process") + +;*---------------------------------------------------------------------*/ +;* flyspell-external-ispell-buffer ... */ +;*---------------------------------------------------------------------*/ +(defvar flyspell-external-ispell-buffer '()) +(defvar flyspell-large-region-buffer '()) +(defvar flyspell-large-region-beg (point-min)) +(defvar flyspell-large-region-end (point-max)) + +;*---------------------------------------------------------------------*/ +;* flyspell-external-point-words ... */ +;*---------------------------------------------------------------------*/ +(defun flyspell-external-point-words () + (let ((buffer flyspell-external-ispell-buffer)) + (set-buffer buffer) + (beginning-of-buffer) + (let ((size (- flyspell-large-region-end flyspell-large-region-beg)) + (start flyspell-large-region-beg)) + ;; now we are done with ispell, we have to find the word in + ;; the initial buffer + (while (< (point) (- (point-max) 1)) + ;; we have to fetch the incorrect word + (if (re-search-forward "\\([^\n]+\\)\n" (point-max) t) + (let ((word (match-string 1))) + (goto-char (match-end 0)) + (set-buffer flyspell-large-region-buffer) + (goto-char flyspell-large-region-beg) + (message "Spell Checking...%d%% [%s]" + (* 100 (/ (float (- (point) start)) size)) + word) + (if (search-forward word flyspell-large-region-end t) + (progn + (setq flyspell-large-region-beg (point)) + (goto-char (- (point) 1)) + (flyspell-word))) + (set-buffer buffer)) + (goto-char (point-max))))) + ;; we are done + (message "Spell Checking completed.") + ;; ok, we are done with pointing out incorrect words, we just + ;; have to kill the temporary buffer + (kill-buffer flyspell-external-ispell-buffer) + (setq flyspell-external-ispell-buffer nil))) + +;*---------------------------------------------------------------------*/ +;* flyspell-large-region ... */ +;*---------------------------------------------------------------------*/ +(defun flyspell-large-region (beg end) + (let* ((curbuf (current-buffer)) + (buffer (get-buffer-create "*flyspell-region*"))) + (setq flyspell-external-ispell-buffer buffer) + (setq flyspell-large-region-buffer curbuf) + (setq flyspell-large-region-beg beg) + (setq flyspell-large-region-end end) + (set-buffer buffer) + (erase-buffer) + ;; this is done, we can start ckecking... + (message "Checking region...") + (set-buffer curbuf) + (let ((c (apply 'call-process-region beg + end + ispell-program-name + nil + buffer + nil + "-l" + (let (args) + ;; Local dictionary becomes the global dictionary in use. + (if ispell-local-dictionary + (setq ispell-dictionary ispell-local-dictionary)) + (setq args (ispell-get-ispell-args)) + (if ispell-dictionary ; use specified dictionary + (setq args + (append (list "-d" ispell-dictionary) args))) + (if ispell-personal-dictionary ; use specified pers dict + (setq args + (append args + (list "-p" + (expand-file-name + ispell-personal-dictionary))))) + (setq args (append args ispell-extra-args)) + args)))) + (if (= c 0) + (flyspell-external-point-words) + (error "Can't check region..."))))) + +;*---------------------------------------------------------------------*/ +;* flyspell-region ... */ +;* ------------------------------------------------------------- */ +;* Because `ispell -a' is too slow, it is not possible to use */ +;* it on large region. Then, when ispell is invoked on a large */ +;* text region, a new `ispell -l' process is spawned. The */ +;* pointed out words are then searched in the region a checked with */ +;* regular flyspell means. */ +;*---------------------------------------------------------------------*/ +(defun flyspell-region (beg end) + "Flyspell text between BEG and END." + (interactive "r") + (if (= beg end) + () + (save-excursion + (if (> beg end) + (let ((old beg)) + (setq beg end) + (setq end old))) + (if (> (- end beg) flyspell-large-region) + (flyspell-large-region beg end) + (flyspell-small-region beg end))))) + +;*---------------------------------------------------------------------*/ ;* flyspell-buffer ... */ ;*---------------------------------------------------------------------*/ (defun flyspell-buffer () @@ -829,6 +1353,49 @@ (flyspell-region (point-min) (point-max))) ;*---------------------------------------------------------------------*/ +;* old next error position ... */ +;*---------------------------------------------------------------------*/ +(defvar flyspell-old-buffer-error nil) +(defvar flyspell-old-pos-error nil) + +;*---------------------------------------------------------------------*/ +;* flyspell-goto-next-error ... */ +;*---------------------------------------------------------------------*/ +(defun flyspell-goto-next-error () + "Go to the next previously detected error. +In general FLYSPELL-GOTO-NEXT-ERROR must be used after +FLYSPELL-BUFFER." + (interactive) + (let ((pos (point)) + (max (point-max))) + (if (and (eq (current-buffer) flyspell-old-buffer-error) + (eq pos flyspell-old-pos-error)) + (progn + (if (= flyspell-old-pos-error max) + ;; goto beginning of buffer + (progn + (message "Restarting from beginning of buffer") + (goto-char (point-min))) + (forward-word 1)) + (setq pos (point)))) + ;; seek the next error + (while (and (< pos max) + (let ((ovs (overlays-at pos)) + (r '())) + (while (and (not r) (consp ovs)) + (if (flyspell-overlay-p (car ovs)) + (setq r t) + (setq ovs (cdr ovs)))) + (not r))) + (setq pos (1+ pos))) + ;; save the current location for next invokation + (setq flyspell-old-pos-error pos) + (setq flyspell-old-buffer-error (current-buffer)) + (goto-char pos) + (if (= pos max) + (message "No more miss-spelled word!")))) + +;*---------------------------------------------------------------------*/ ;* flyspell-overlay-p ... */ ;*---------------------------------------------------------------------*/ (defun flyspell-overlay-p (o) @@ -860,7 +1427,8 @@ (if (flyspell-overlay-p (car overlays)) (delete-overlay (car overlays))) (setq overlays (cdr overlays)))) - (delete-overlay flyspell-overlay))) + (if (flyspell-overlay-p flyspell-overlay) + (delete-overlay flyspell-overlay)))) ;*---------------------------------------------------------------------*/ ;* flyspell-properties-at-p ... */ @@ -895,29 +1463,31 @@ (if flyspell-use-local-map (overlay-put flyspell-overlay flyspell-overlay-keymap-property-name - flyspell-local-mouse-map)))) + flyspell-local-mouse-map)) + flyspell-overlay)) ;*---------------------------------------------------------------------*/ ;* flyspell-highlight-incorrect-region ... */ ;*---------------------------------------------------------------------*/ -(defun flyspell-highlight-incorrect-region (beg end) +(defun flyspell-highlight-incorrect-region (beg end poss) "Set up an overlay on a misspelled word, in the buffer from BEG to END." - (run-hook-with-args 'flyspell-incorrect-hook beg end) - (if (or flyspell-highlight-properties (not (flyspell-properties-at-p beg))) - (progn - ;; we cleanup current overlay at the same position - (if (and (not flyspell-persistent-highlight) - (overlayp flyspell-overlay)) - (delete-overlay flyspell-overlay) - (let ((overlays (overlays-at beg))) - (while (consp overlays) - (if (flyspell-overlay-p (car overlays)) - (delete-overlay (car overlays))) - (setq overlays (cdr overlays))))) - ;; now we can use a new overlay - (setq flyspell-overlay - (make-flyspell-overlay beg end - 'flyspell-incorrect-face 'highlight))))) + (unless (run-hook-with-args-until-success + 'flyspell-incorrect-hook beg end poss) + (if (or flyspell-highlight-properties (not (flyspell-properties-at-p beg))) + (progn + ;; we cleanup current overlay at the same position + (if (and (not flyspell-persistent-highlight) + (overlayp flyspell-overlay)) + (delete-overlay flyspell-overlay) + (let ((overlays (overlays-at beg))) + (while (consp overlays) + (if (flyspell-overlay-p (car overlays)) + (delete-overlay (car overlays))) + (setq overlays (cdr overlays))))) + ;; now we can use a new overlay + (setq flyspell-overlay + (make-flyspell-overlay beg end + 'flyspell-incorrect-face 'highlight)))))) ;*---------------------------------------------------------------------*/ ;* flyspell-highlight-duplicate-region ... */ @@ -946,84 +1516,174 @@ (defvar flyspell-auto-correct-pos nil) (defvar flyspell-auto-correct-region nil) (defvar flyspell-auto-correct-ring nil) +(defvar flyspell-auto-correct-word nil) +(make-variable-buffer-local 'flyspell-auto-correct-pos) +(make-variable-buffer-local 'flyspell-auto-correct-region) +(make-variable-buffer-local 'flyspell-auto-correct-ring) +(make-variable-buffer-local 'flyspell-auto-correct-word) + +;*---------------------------------------------------------------------*/ +;* flyspell-check-previous-highlighted-word ... */ +;*---------------------------------------------------------------------*/ +(defun flyspell-check-previous-highlighted-word (&optional arg) + "Correct the closer mispelled word. +This function scans a mis-spelled word before the cursor. If it finds one +it proposes replacement for that word. With prefix arg, count that many +misspelled words backwards." + (interactive) + (let ((pos1 (point)) + (pos (point)) + (arg (if (or (not (numberp arg)) (< arg 1)) 1 arg)) + ov ovs) + (if (catch 'exit + (while (and (setq pos (previous-overlay-change pos)) + (not (= pos pos1))) + (setq pos1 pos) + (if (> pos (point-min)) + (progn + (setq ovs (overlays-at (1- pos))) + (while (consp ovs) + (setq ov (car ovs)) + (setq ovs (cdr ovs)) + (if (and (overlay-get ov 'flyspell-overlay) + (= 0 (setq arg (1- arg)))) + (throw 'exit t))))))) + (save-excursion + (goto-char pos) + (ispell-word)) + (error "No word to correct before point.")))) + +;*---------------------------------------------------------------------*/ +;* flyspell-display-next-corrections ... */ +;*---------------------------------------------------------------------*/ +(defun flyspell-display-next-corrections (corrections) + (let ((string "Corrections:") + (l corrections) + (pos '())) + (while (< (length string) 80) + (if (equal (car l) flyspell-auto-correct-word) + (setq pos (cons (+ 1 (length string)) pos))) + (setq string (concat string " " (car l))) + (setq l (cdr l))) + (while (consp pos) + (let ((num (car pos))) + (put-text-property num + (+ num (length flyspell-auto-correct-word)) + 'face + 'flyspell-incorrect-face + string)) + (setq pos (cdr pos))) + (if (fboundp 'display-message) + (display-message 'no-log string) + (message string)))) + +;*---------------------------------------------------------------------*/ +;* flyspell-abbrev-table ... */ +;*---------------------------------------------------------------------*/ +(defun flyspell-abbrev-table () + (if flyspell-use-global-abbrev-table-p + global-abbrev-table + local-abbrev-table)) ;*---------------------------------------------------------------------*/ ;* flyspell-auto-correct-word ... */ ;*---------------------------------------------------------------------*/ -(defun flyspell-auto-correct-word (pos) - "Correct the word at POS. -This command proposes various successive corrections for the word at POS. -The variable `flyspell-auto-correct-binding' specifies the key to bind -to this command." - (interactive "d") - ;; use the correct dictionary - (ispell-accept-buffer-local-defs) - (if (eq flyspell-auto-correct-pos pos) - ;; we have already been using the function at the same location - (progn - (save-excursion - (let ((start (car flyspell-auto-correct-region)) - (len (cdr flyspell-auto-correct-region))) - (delete-region start (+ start len)) - (setq flyspell-auto-correct-ring (cdr flyspell-auto-correct-ring)) - (let* ((word (car flyspell-auto-correct-ring)) - (len (length word))) - (rplacd flyspell-auto-correct-region len) - (goto-char start) - (insert word)))) - (setq flyspell-auto-correct-pos (point))) - ;; retain cursor location - (let ((cursor-location pos) - (word (flyspell-get-word nil)) - start end poss) - ;; destructure return word info list. - (setq start (car (cdr word)) - end (car (cdr (cdr word))) - word (car word)) - ;; now check spelling of word. - (process-send-string ispell-process "%\n") ;put in verbose mode - (process-send-string ispell-process (concat "^" word "\n")) - ;; wait until ispell has processed word - (while (progn - (accept-process-output ispell-process) - (not (string= "" (car ispell-filter))))) - (setq ispell-filter (cdr ispell-filter)) - (if (listp 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")) - (t - ;; the word is incorrect, we have to propose a replacement - (let ((replacements (if flyspell-sort-corrections - (sort (car (cdr (cdr poss))) 'string<) - (car (cdr (cdr poss)))))) - (if (consp replacements) - (progn - (let ((replace (car replacements))) - (setq word replace) - (setq cursor-location (+ (- (length word) (- end start)) - cursor-location)) - (if (not (equal word (car poss))) - (progn - ;; the save the current replacements - (setq flyspell-auto-correct-pos cursor-location) - (setq flyspell-auto-correct-region - (cons start (length word))) - (let ((l replacements)) - (while (consp (cdr l)) - (setq l (cdr l))) - (rplacd l (cons (car poss) replacements))) - (setq flyspell-auto-correct-ring - (cdr replacements)) - (delete-region start end) - (insert word))))))))) - ;; return to original location - (goto-char cursor-location) - (ispell-pdict-save t)))) +(defun flyspell-auto-correct-word () + "Correct the current word. +This command proposes various successive corrections for the current word." + (interactive) + (let ((pos (point)) + (old-max (point-max))) + ;; use the correct dictionary + (flyspell-accept-buffer-local-defs) + (if (and (eq flyspell-auto-correct-pos pos) + (consp flyspell-auto-correct-region)) + ;; we have already been using the function at the same location + (let* ((start (car flyspell-auto-correct-region)) + (len (cdr flyspell-auto-correct-region))) + (delete-region start (+ start len)) + (setq flyspell-auto-correct-ring (cdr flyspell-auto-correct-ring)) + (let* ((word (car flyspell-auto-correct-ring)) + (len (length word))) + (rplacd flyspell-auto-correct-region len) + (goto-char start) + (if flyspell-abbrev-p + (if (flyspell-already-abbrevp (flyspell-abbrev-table) + flyspell-auto-correct-word) + (flyspell-change-abbrev (flyspell-abbrev-table) + flyspell-auto-correct-word + word) + (define-abbrev (flyspell-abbrev-table) + flyspell-auto-correct-word word))) + (insert word) + (flyspell-word) + (flyspell-display-next-corrections flyspell-auto-correct-ring)) + (flyspell-ajust-cursor-point pos (point) old-max) + (setq flyspell-auto-correct-pos (point))) + ;; fetch the word to be checked + (let ((word (flyspell-get-word nil)) + start end poss) + ;; destructure return word info list. + (setq start (car (cdr word)) + end (car (cdr (cdr word))) + word (car word)) + (setq flyspell-auto-correct-word word) + ;; now check spelling of word. + (process-send-string ispell-process "%\n") ;put in verbose mode + (process-send-string ispell-process (concat "^" word "\n")) + ;; wait until ispell has processed word + (while (progn + (accept-process-output ispell-process) + (not (string= "" (car ispell-filter))))) + (setq ispell-filter (cdr 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")) + (t + ;; the word is incorrect, we have to propose a replacement + (let ((replacements (if flyspell-sort-corrections + (sort (car (cdr (cdr poss))) 'string<) + (car (cdr (cdr poss)))))) + (setq flyspell-auto-correct-region nil) + (if (consp replacements) + (progn + (let ((replace (car replacements))) + (let ((new-word replace)) + (if (not (equal new-word (car poss))) + (progn + ;; the save the current replacements + (setq flyspell-auto-correct-region + (cons start (length new-word))) + (let ((l replacements)) + (while (consp (cdr l)) + (setq l (cdr l))) + (rplacd l (cons (car poss) replacements))) + (setq flyspell-auto-correct-ring + replacements) + (delete-region start end) + (insert new-word) + (if flyspell-abbrev-p + (if (flyspell-already-abbrevp + (flyspell-abbrev-table) word) + (flyspell-change-abbrev + (flyspell-abbrev-table) + word + new-word) + (define-abbrev (flyspell-abbrev-table) + word new-word))) + (flyspell-word) + (flyspell-display-next-corrections + (cons new-word flyspell-auto-correct-ring)) + (flyspell-ajust-cursor-point pos + (point) + old-max)))))))))) + (setq flyspell-auto-correct-pos (point)) + (ispell-pdict-save t))))) ;*---------------------------------------------------------------------*/ ;* flyspell-correct-word ... */ @@ -1038,9 +1698,9 @@ This will check or reload the dictionary. Use \\[ispell-change-dictionary] or \\[ispell-region] to update the Ispell process." (interactive "e") - (if flyspell-use-local-map + (if (eq flyspell-emacs 'xemacs) (flyspell-correct-word/mouse-keymap event) - (flyspell-correct-word/local-keymap event))) + (flyspell-correct-word/local-keymap event))) ;*---------------------------------------------------------------------*/ ;* flyspell-correct-word/local-keymap ... */ @@ -1077,14 +1737,14 @@ (command-execute (key-binding (this-command-keys)))))))))) ;*---------------------------------------------------------------------*/ -;* flyspell-correct-word ... */ +;* flyspell-correct-word/mouse-keymap ... */ ;*---------------------------------------------------------------------*/ (defun flyspell-correct-word/mouse-keymap (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 - (ispell-accept-buffer-local-defs) + (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) @@ -1103,7 +1763,7 @@ (accept-process-output ispell-process) (not (string= "" (car ispell-filter))))) (setq ispell-filter (cdr ispell-filter)) - (if (listp 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 @@ -1115,8 +1775,10 @@ ;; the word is incorrect, we have to propose a replacement (setq replace (flyspell-emacs-popup event poss word)) (cond ((eq replace 'ignore) + (goto-char save) nil) ((eq replace 'save) + (goto-char save) (process-send-string ispell-process (concat "*" word "\n")) (flyspell-unhighlight-at cursor-location) (setq ispell-pdict-modified-p '(t))) @@ -1126,33 +1788,45 @@ (setq ispell-pdict-modified-p (list ispell-pdict-modified-p))) (flyspell-unhighlight-at cursor-location) + (goto-char save) (if (eq replace 'buffer) (ispell-add-per-file-word-list word))) (replace - (setq word (if (atom replace) replace (car replace)) - cursor-location (+ (- (length word) (- end start)) - cursor-location)) - (if (not (equal word (car poss))) - (progn - (delete-region start end) - (insert word)))))) + (let ((new-word (if (atom replace) + replace + (car replace))) + (cursor-location (+ (- (length word) (- end start)) + cursor-location))) + (if (not (equal new-word (car poss))) + (let ((old-max (point-max))) + (delete-region start end) + (insert new-word) + (if flyspell-abbrev-p + (define-abbrev (flyspell-abbrev-table) + word + new-word)) + (flyspell-ajust-cursor-point save + cursor-location + old-max))))) + (t + (goto-char save) + nil))) ((eq flyspell-emacs 'xemacs) (flyspell-xemacs-popup - event poss word cursor-location start end))) - (ispell-pdict-save t)) - (if (< save (point-max)) - (goto-char save) - (goto-char (point-max))))) + event poss word cursor-location start end save) + (goto-char save))) + (ispell-pdict-save t)))) ;*---------------------------------------------------------------------*/ ;* flyspell-xemacs-correct ... */ ;*---------------------------------------------------------------------*/ -(defun flyspell-xemacs-correct (replace poss word cursor-location start end) +(defun flyspell-xemacs-correct (replace poss word cursor-location start end save) "The xemacs popup menu callback." (cond ((eq replace 'ignore) nil) ((eq replace 'save) (process-send-string ispell-process (concat "*" word "\n")) + (process-send-string ispell-process "#\n") (flyspell-unhighlight-at cursor-location) (setq ispell-pdict-modified-p '(t))) ((or (eq replace 'buffer) (eq replace 'session)) @@ -1164,14 +1838,36 @@ (if (eq replace 'buffer) (ispell-add-per-file-word-list word))) (replace - (setq word (if (atom replace) replace (car replace)) - cursor-location (+ (- (length word) (- end start)) - cursor-location)) - (if (not (equal word (car poss))) - (save-excursion - (delete-region start end) - (goto-char start) - (insert word)))))) + (let ((old-max (point-max)) + (new-word (if (atom replace) + replace + (car replace))) + (cursor-location (+ (- (length word) (- end start)) + cursor-location))) + (if (not (equal new-word (car poss))) + (progn + (delete-region start end) + (goto-char start) + (insert new-word) + (if flyspell-abbrev-p + (define-abbrev (flyspell-abbrev-table) + word + new-word)))) + (flyspell-ajust-cursor-point save cursor-location old-max))))) + +;*---------------------------------------------------------------------*/ +;* flyspell-ajust-cursor-point ... */ +;*---------------------------------------------------------------------*/ +(defun flyspell-ajust-cursor-point (save cursor-location old-max) + (if (>= save cursor-location) + (let ((new-pos (+ save (- (point-max) old-max)))) + (goto-char (cond + ((< new-pos (point-min)) + (point-min)) + ((> new-pos (point-max)) + (point-max)) + (t new-pos)))) + (goto-char save))) ;*---------------------------------------------------------------------*/ ;* flyspell-emacs-popup ... */ @@ -1183,7 +1879,7 @@ (mouse-pos (if (nth 1 mouse-pos) mouse-pos (set-mouse-position (car mouse-pos) - (/ (frame-width) 2) 2) + (/ (frame-width) 2) 2) (unfocus-frame) (mouse-position)))) (setq event (list (list (car (cdr mouse-pos)) @@ -1219,7 +1915,7 @@ ;*---------------------------------------------------------------------*/ ;* flyspell-xemacs-popup ... */ ;*---------------------------------------------------------------------*/ -(defun flyspell-xemacs-popup (event poss word cursor-location start end) +(defun flyspell-xemacs-popup (event poss word cursor-location start end save) "The xemacs popup menu." (let* ((corrects (if flyspell-sort-corrections (sort (car (cdr (cdr poss))) 'string<) @@ -1233,7 +1929,8 @@ word cursor-location start - end) + end + save) t)) corrects) '())) @@ -1247,7 +1944,8 @@ word cursor-location start - end) + end + save) t) (vector "Save word" @@ -1257,7 +1955,8 @@ word cursor-location start - end) + end + save) t))) (session (vector "Accept (session)" (list 'flyspell-xemacs-correct @@ -1266,7 +1965,8 @@ word cursor-location start - end) + end + save) t)) (buffer (vector "Accept (buffer)" (list 'flyspell-xemacs-correct @@ -1275,7 +1975,8 @@ word cursor-location start - end) + end + save) t))) (if (consp cor-menu) (append cor-menu (list "-" save session buffer)) @@ -1284,6 +1985,63 @@ ispell-dictionary)) menu)))) -(provide 'flyspell) +;*---------------------------------------------------------------------*/ +;* Some example functions for real autocrrecting */ +;*---------------------------------------------------------------------*/ +(defun flyspell-maybe-correct-transposition (beg end poss) + "Apply 'transpose-chars' to all points in the region BEG to END and +return t if any those result in a possible replacement suggested by ispell +in POSS. Otherwise the change is undone. + +This function is meant to be added to 'flyspell-incorrect-hook'." + (when (consp poss) + (catch 'done + (save-excursion + (goto-char (1+ beg)) + (while (< (point) end) + (transpose-chars 1) + (when (member (buffer-substring beg end) (nth 2 poss)) + (throw 'done t)) + (transpose-chars -1) + (forward-char)) + nil)))) + +(defun flyspell-maybe-correct-doubling (beg end poss) + "For each doubled charachter in the region BEG to END, remove one and +return t if any those result in a possible replacement suggested by ispell +in POSS. Otherwise the change is undone. +This function is meant to be added to 'flyspell-incorrect-hook'." + (when (consp poss) + (catch 'done + (save-excursion + (let ((last (char-after beg)) + this) + (goto-char (1+ beg)) + (while (< (point) end) + (setq this (char-after)) + (if (not (char-equal this last)) + (forward-char) + (delete-char 1) + (when (member (buffer-substring beg (1- end)) (nth 2 poss)) + (throw 'done t)) + ;; undo + (insert-char this 1)) + (setq last this)) + nil))))) + +;*---------------------------------------------------------------------*/ +;* flyspell-already-abbrevp ... */ +;*---------------------------------------------------------------------*/ +(defun flyspell-already-abbrevp (table word) + (let ((sym (abbrev-symbol word table))) + (and sym (symbolp sym)))) + +;*---------------------------------------------------------------------*/ +;* flyspell-change-abbrev ... */ +;*---------------------------------------------------------------------*/ +(defun flyspell-change-abbrev (table old new) + (set (abbrev-symbol old table) new)) + +(provide 'flyspell) ;;; flyspell.el ends here