home *** CD-ROM | disk | FTP | other *** search
/ BCI NET 2 / BCI NET 2.iso / archives / programming / source / a2.0bemacs-src.lha / Emacs-19.25 / lisp / ispell4.el < prev    next >
Encoding:
Text File  |  1994-05-20  |  38.1 KB  |  1,088 lines

  1. ;;; ispell.el --- this is the GNU EMACS interface to GNU ISPELL version 4.
  2.  
  3. ;;Copyright (C) 1990, 1991, 1993 Free Software Foundation, Inc.
  4.  
  5. ;; Keywords: wp
  6.  
  7. ;;This file is part of GNU Emacs.
  8. ;;
  9. ;;GNU Emacs is free software; you can redistribute it and/or modify
  10. ;;it under the terms of the GNU General Public License as published by
  11. ;;the Free Software Foundation; either version 2, or (at your option)
  12. ;;any later version.
  13. ;;
  14. ;;GNU Emacs is distributed in the hope that it will be useful,
  15. ;;but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;;MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17. ;;GNU General Public License for more details.
  18. ;;
  19. ;;You should have received a copy of the GNU General Public License
  20. ;;along with GNU Emacs; see the file COPYING.  If not, write to
  21. ;;the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
  22.  
  23. ;;; Commentary:
  24.  
  25. ;; This package provides a graceful interface to ispell, the GNU
  26. ;; spelling checker.
  27.  
  28. ;;; Code:
  29.  
  30. (defvar ispell-have-new-look t
  31.   "Non-nil means use the `-r' option when running `look'.")
  32.  
  33. (defvar ispell-enable-tex-parser nil
  34.   "Non-nil enables experimental TeX parser in Ispell for TeX-mode buffers.")
  35.  
  36. (defvar ispell-process nil "The process running Ispell")
  37. (defvar ispell-next-message nil
  38.   "An integer: where in `*ispell*' buffer to find next message from Ispell.")
  39.  
  40. (defvar ispell-command "ispell"
  41.   "Command for running Ispell.")
  42. (defvar ispell-command-options nil
  43.   "*String (or list of strings) to pass to Ispell as command arguments.
  44. You can specify your private dictionary via the -p <filename> option.
  45. The -S option is always passed to Ispell as the last parameter,
  46. and need not be mentioned here.")
  47.  
  48. (defvar ispell-look-command "look"
  49.   "*Command for running look.")
  50.  
  51. ;Each marker in this list points to the start of a word that
  52. ;ispell thought was bad last time it did the :file command.
  53. ;Notice that if the user accepts or inserts a word into his
  54. ;private dictionary, then some "good" words will be on the list.
  55. ;We would like to deal with this by looking up the words again just before
  56. ;presenting them to the user, but that is too slow on machines
  57. ;without the select system call.  Therefore, see the variable
  58. ;ispell-recently-accepted.
  59. (defvar ispell-bad-words nil
  60.   "A list of markers reflecting the output of the Ispell `:file' command.")
  61.  
  62. ;list of words that the user has accepted, but that might still
  63. ;be on the bad-words list
  64. (defvar ispell-recently-accepted nil)
  65.  
  66. ;; Non-nil means we have started showing an alternatives window.
  67. ;; This is the window config from before then.
  68. (defvar ispell-window-configuration nil)
  69.  
  70. ;t when :dump command needed
  71. (defvar ispell-dump-needed nil)
  72.  
  73. (defun ispell-flush-bad-words ()
  74.   (while ispell-bad-words
  75.     (if (markerp (car ispell-bad-words))
  76.         (set-marker (car ispell-bad-words) nil))
  77.     (setq ispell-bad-words (cdr ispell-bad-words)))
  78.   (setq ispell-recently-accepted nil))
  79.  
  80. (defun kill-ispell ()
  81.   "Kill the Ispell process.
  82. Any changes in your private dictionary
  83. that have not already been dumped will be lost."
  84.   (interactive)
  85.   (if ispell-process
  86.       (delete-process ispell-process))
  87.   (setq ispell-process nil)
  88.   (ispell-flush-bad-words))
  89.  
  90. (put 'ispell-startup-error 'error-conditions
  91.      '(ispell-startup-error error))
  92. (put 'ispell-startup-error 'error-message
  93.      "Problem starting ispell - see buffer *ispell*")
  94.  
  95. ;; Start an ispell subprocess; check the version; and display the greeting.
  96.  
  97. (defun start-ispell ()
  98.   (message "Starting ispell ...")
  99.   (let ((buf (get-buffer "*ispell*")))
  100.     (if buf
  101.     (kill-buffer buf)))
  102.   (condition-case err
  103.       (setq ispell-process
  104.         (apply 'start-process "ispell" "*ispell*" ispell-command
  105.            (append (if (listp ispell-command-options)
  106.                    ispell-command-options
  107.                  (list ispell-command-options))
  108.                '("-S"))))
  109.     (file-error (signal 'ispell-startup-error nil)))
  110.   (process-kill-without-query ispell-process)
  111.   (buffer-disable-undo (process-buffer ispell-process))
  112.   (accept-process-output ispell-process)
  113.   (let (last-char)
  114.     (save-excursion
  115.       (set-buffer (process-buffer ispell-process))
  116.       (bury-buffer (current-buffer))
  117.       (setq last-char (- (point-max) 1))
  118.       (while (not (eq (char-after last-char) ?=))
  119.     (cond ((not (eq (process-status ispell-process) 'run))
  120.            (kill-ispell)
  121.            (signal 'ispell-startup-error nil)))
  122.     (accept-process-output ispell-process)
  123.     (setq last-char (- (point-max) 1)))
  124.       (goto-char (point-min))
  125.       (let ((greeting (read (current-buffer))))
  126.     (if (not (= (car greeting) 1))
  127.         (error "Bad ispell version: wanted 1, got %d" (car greeting)))
  128.     (message (car (cdr greeting))))
  129.       (delete-region (point-min) last-char))))
  130.   
  131. ;; Make sure ispell is ready for a command.
  132. ;; Leaves buffer set to *ispell*, point at '='.
  133.  
  134. (defun ispell-sync (intr)
  135.   (if (or (null ispell-process)
  136.       (not (eq (process-status ispell-process) 'run)))
  137.       (start-ispell))
  138.   (if intr
  139.       (interrupt-process ispell-process))
  140.   (let (last-char)
  141.     (set-buffer (process-buffer ispell-process))
  142.     (bury-buffer (current-buffer))
  143.     (setq last-char (- (point-max) 1))
  144.     (while (not (eq (char-after last-char) ?=))
  145.       (accept-process-output ispell-process)
  146.       (setq last-char (- (point-max) 1)))
  147.     (goto-char last-char)))
  148.  
  149. ;; Send a command to ispell.  Choices are:
  150. ;; 
  151. ;; WORD        Check spelling of WORD.  Result is
  152. ;; 
  153. ;;             nil               not found
  154. ;;             t               spelled ok
  155. ;;             list of strings           near misses
  156. ;; 
  157. ;; :file FILENAME    scan the named file, and print the file offsets of
  158. ;;         any misspelled words
  159. ;; 
  160. ;; :insert WORD    put word in private dictionary
  161. ;; 
  162. ;; :accept WORD    don't complain about word any more this session
  163. ;; 
  164. ;; :dump        write out the current private dictionary, if necessary.
  165. ;; 
  166. ;; :reload        reread private dictionary (default: `~/ispell.words')
  167. ;; 
  168. ;; :tex
  169. ;; :troff
  170. ;; :generic    set type of parser to use when scanning whole files
  171.  
  172. (defun ispell-cmd (&rest strings)
  173.   (save-excursion
  174.     (ispell-sync t)
  175.     (set-buffer (process-buffer ispell-process))
  176.     (bury-buffer (current-buffer))
  177.     (erase-buffer)
  178.     (setq ispell-next-message (point-min))
  179.     (while strings
  180.       (process-send-string ispell-process (car strings))
  181.       (setq strings (cdr strings)))
  182.     (process-send-string ispell-process "\n")
  183.     (accept-process-output ispell-process)
  184.     (ispell-sync nil)))
  185.  
  186. (defun ispell-dump ()
  187.   (cond (ispell-dump-needed
  188.      (setq ispell-dump-needed nil)
  189.      (ispell-cmd ":dump"))))
  190.  
  191. (defun ispell-insert (word)
  192.   (ispell-cmd ":insert " word)
  193.   (if ispell-bad-words
  194.       (setq ispell-recently-accepted (cons word ispell-recently-accepted)))
  195.   (setq ispell-dump-needed t))
  196.  
  197. (defun ispell-accept (word)
  198.   (ispell-cmd ":accept " word)
  199.   (if ispell-bad-words
  200.       (setq ispell-recently-accepted (cons word ispell-recently-accepted))))
  201.  
  202. ;; Return the next message sent by the Ispell subprocess.
  203.  
  204. (defun ispell-next-message ()
  205.   (save-excursion
  206.     (set-buffer (process-buffer ispell-process))
  207.     (bury-buffer (current-buffer))
  208.     (save-restriction
  209.       (goto-char ispell-next-message)
  210.       (narrow-to-region (point)
  211.                         (progn (forward-sexp 1) (point)))
  212.       (setq ispell-next-message (point))
  213.       (goto-char (point-min))
  214.       (read (current-buffer)))))
  215.  
  216. (defun ispell-tex-buffer-p ()
  217.   (memq major-mode '(plain-TeX-mode LaTeX-mode)))
  218.  
  219. (defvar ispell-menu-map (make-sparse-keymap "Spell"))
  220. (defalias 'ispell-menu-map ispell-menu-map)
  221.  
  222. (define-key ispell-menu-map [ispell-complete-word-interior-frag]
  223.   '("Complete Interior Fragment" . ispell-complete-word-interior-frag))
  224.  
  225. (define-key ispell-menu-map [ispell-complete-word]
  226.   '("Complete Word" . ispell-complete-word))
  227.  
  228. (define-key ispell-menu-map [reload-ispell]
  229.   '("Reload Dictionary" . reload-ispell))
  230.  
  231. (define-key ispell-menu-map [ispell-next]
  232.   '("Continue Check" . ispell-next))
  233.  
  234. (define-key ispell-menu-map [ispell-message]
  235.   '("Check Message" . ispell-message))
  236.  
  237. (define-key ispell-menu-map [ispell-region]
  238.   '("Check Region" . ispell-region))
  239.  
  240. (define-key ispell-menu-map [ispell-buffer]
  241.   '("Check Buffer" . ispell))
  242.  
  243. (define-key ispell-menu-map [ispell-word]
  244.   '("Check Word" . ispell-word))
  245.  
  246. ;;;###autoload
  247. (defun ispell (&optional buf start end)
  248.   "Run Ispell over current buffer's visited file.
  249. First the file is scanned for misspelled words, then Ispell
  250. enters a loop with the following commands for every misspelled word:
  251.  
  252. DIGIT    Near miss selector.  If the misspelled word is close to
  253.     some words in the dictionary, they are offered as near misses.
  254. r    Replace.  Replace the word with a string you type.  Each word
  255.     of your new string is also checked.
  256. i    Insert.  Insert this word in your private dictionary (by default,
  257.     `$HOME/ispell.words').
  258. a    Accept.  Accept this word for the rest of this editing session,
  259.      but don't put it in your private dictionary.
  260. l    Lookup.  Look for a word in the dictionary by fast binary
  261.     search, or search for a regular expression in the dictionary
  262.     using grep.
  263. SPACE    Accept the word this time, but complain if it is seen again.
  264. q, \\[keyboard-quit]    Leave the command loop.  You can come back later with \\[ispell-next]."
  265.   (interactive)
  266.   (if (null start)
  267.       (setq start 0))
  268.   (if (null end)
  269.       (setq end 0))
  270.  
  271.   (if (null buf)
  272.       (setq buf (current-buffer)))
  273.   (setq buf (get-buffer buf))
  274.   (if (null buf)
  275.       (error "Can't find buffer"))
  276.   ;; Deactivate the mark, because we'll do it anyway if we change something,
  277.   ;; and a region highlight while in the Ispell loop is distracting.
  278.   (deactivate-mark)
  279.   (save-excursion
  280.     (set-buffer buf)
  281.     (let ((filename buffer-file-name)
  282.       (delete-temp nil))
  283.       (unwind-protect
  284.       (progn
  285.         (cond ((or (null filename)
  286.                (find-file-name-handler buffer-file-name nil))
  287.            (setq filename (make-temp-name "/usr/tmp/ispell"))
  288.            (setq delete-temp t)
  289.            (write-region (point-min) (point-max) filename))
  290.           ((and (buffer-modified-p buf)
  291.             (y-or-n-p (format "Save file %s? " filename)))
  292.            (save-buffer)))
  293.         (message "Ispell scanning file...")
  294.         (if (and ispell-enable-tex-parser
  295.              (ispell-tex-buffer-p))
  296.         (ispell-cmd ":tex")
  297.           (ispell-cmd ":generic"))
  298.         (ispell-cmd (format ":file %s %d %d" filename start end)))
  299.     (if delete-temp
  300.         (condition-case ()
  301.         (delete-file filename)
  302.           (file-error nil)))))
  303.     (message "Parsing ispell output ...")
  304.     (ispell-flush-bad-words)
  305.     (let (pos bad-words)
  306.       (while (numberp (setq pos (ispell-next-message)))
  307.     ;;ispell may check the words on the line following the end
  308.     ;;of the region - therefore, don't record anything out of range
  309.     (if (or (= end 0)
  310.         (< pos end))
  311.         (setq bad-words (cons (set-marker (make-marker) (+ pos 1))
  312.                   bad-words))))
  313.       (setq bad-words (cons pos bad-words))
  314.       (setq ispell-bad-words (nreverse bad-words))))
  315.   (cond ((not (markerp (car ispell-bad-words)))
  316.      (setq ispell-bad-words nil)
  317.      (message "No misspellings.")
  318.      t)
  319.     (t
  320.      (message "Ispell parsing done.")
  321.      (ispell-next))))
  322.  
  323. ;;;###autoload
  324. (defalias 'ispell-buffer 'ispell)
  325.  
  326. (defun ispell-next ()
  327.   "Resume command loop for most recent Ispell command.
  328. Return value is t unless exit is due to typing `q'."
  329.   (interactive)
  330.   (setq ispell-window-configuration nil)
  331.   (prog1
  332.       (unwind-protect
  333.       (catch 'ispell-quit
  334.         ;; There used to be a save-excursion here,
  335.         ;; but that was annoying: it's better if point doesn't move
  336.         ;; when you type q.
  337.         (let (next)
  338.           (while (markerp (setq next (car ispell-bad-words)))
  339.         (switch-to-buffer (marker-buffer next))
  340.         (push-mark)
  341.         (ispell-point next "at saved position.")
  342.         (setq ispell-bad-words (cdr ispell-bad-words))
  343.         (set-marker next nil)))
  344.         t)
  345.     (ispell-dehighlight)
  346.     (if ispell-window-configuration
  347.         (set-window-configuration ispell-window-configuration))
  348.     (cond ((null ispell-bad-words)
  349.            (error "Ispell has not yet been run"))
  350.           ((markerp (car ispell-bad-words))
  351.            (message (substitute-command-keys
  352.                "Type \\[ispell-next] to continue")))
  353.           ((eq (car ispell-bad-words) nil)
  354.            (setq ispell-bad-words nil)
  355.            (message "No more misspellings (but checker was interrupted)"))
  356.           ((eq (car ispell-bad-words) t)
  357.            (setq ispell-bad-words nil)
  358.            (message "Ispell done"))
  359.           (t
  360.            (setq ispell-bad-words nil)
  361.            (message "Bad ispell internal list"))))
  362.     (ispell-dump)))
  363.  
  364. ;;;###autoload
  365. (defun ispell-word (&optional resume)
  366.   "Check the spelling of the word under the cursor.
  367. See the command `ispell' for more information.
  368. With a prefix argument, resume handling of the previous Ispell command."
  369.   (interactive "P")
  370.   (if resume
  371.       (ispell-next)
  372.     (condition-case err
  373.     (catch 'ispell-quit
  374.       (save-window-excursion
  375.         (ispell-point (point) "at point."))
  376.       (ispell-dump))
  377.       (ispell-startup-error
  378.        (cond ((y-or-n-p "Problem starting ispell, use old-style spell instead? ")
  379.           (load-library "spell")
  380.           (define-key esc-map "$" 'spell-word)
  381.           (spell-word)))))))
  382.  
  383. ;;;###autoload (define-key esc-map "$" 'ispell-word)
  384.  
  385. ;;;###autoload
  386. (defun ispell-region (start &optional end)
  387.   "Check the spelling for all of the words in the region."
  388.   (interactive "r")
  389.   (ispell (current-buffer) start end))
  390.  
  391. (defun ispell-letterp (c)
  392.   (and c
  393.        (or (and (>= c ?A) (<= c ?Z))
  394.        (and (>= c ?a) (<= c ?z))
  395.        (>= c 128))))
  396.  
  397. (defun ispell-letter-or-quotep (c)
  398.   (and c
  399.        (or (and (>= c ?A) (<= c ?Z))
  400.        (and (>= c ?a) (<= c ?z))
  401.        (= c ?')
  402.        (>= c 128))))
  403.  
  404. (defun ispell-find-word-start ()
  405.   ;;backward to a letter
  406.   (if (not (ispell-letterp (char-after (point))))
  407.       (while (and (not (bobp))
  408.           (not (ispell-letterp (char-after (- (point) 1)))))
  409.     (backward-char)))
  410.   ;;backward to beginning of word
  411.   (while (ispell-letter-or-quotep (char-after (- (point) 1)))
  412.     (backward-char))
  413.   (skip-chars-forward "'"))
  414.  
  415. (defun ispell-find-word-end ()
  416.   (while (ispell-letter-or-quotep (char-after (point)))
  417.     (forward-char))
  418.   (skip-chars-backward "'"))
  419.  
  420. (defun ispell-next-word ()
  421.   (while (and (not (eobp))
  422.           (not (ispell-letterp (char-after (point)))))
  423.     (forward-char)))
  424.  
  425. ;if end is nil, then do one word at start
  426. ;otherwise, do all words from the beginning of the word where
  427. ;start points, to the end of the word where end points
  428. (defun ispell-point (start message)
  429.   (let ((wend (make-marker))
  430.     rescan
  431.     end)
  432.     ;; There used to be a save-excursion here,
  433.     ;; but that was annoying: it's better if point doesn't move
  434.     ;; when you type q.
  435.     (goto-char start)
  436.     (ispell-find-word-start)        ;find correct word start
  437.     (setq start (point-marker))
  438.     (ispell-find-word-end)        ;now find correct end
  439.     (setq end (point-marker))
  440.     ;; Do nothing if we don't find a word.
  441.     (if (< start end)
  442.     (while (< start end)
  443.       (goto-char start)
  444.       (ispell-find-word-end)    ;find end of current word
  445.                     ;could be before 'end' if
  446.                     ;user typed replacement
  447.                     ;that is more than one word
  448.       (set-marker wend (point))
  449.       (setq rescan nil)
  450.       (setq word (buffer-substring start wend))
  451.       (cond ((ispell-still-bad word)
  452. ;;; This just causes confusion. -- rms.
  453. ;;;         (goto-char start)
  454. ;;;         (sit-for 0)
  455.          (message (format "Ispell checking %s" word))
  456.          (ispell-cmd word)
  457.          (let ((message (ispell-next-message)))
  458.            (cond ((eq message t)
  459.               (message "%s: ok" word))
  460.              ((or (null message)
  461.                   (consp message))
  462.               (setq rescan
  463.                 (ispell-command-loop word start wend message)))
  464.              (t
  465.               (error "unknown ispell response %s" message))))))
  466.       (cond ((null rescan)
  467.          (goto-char wend)
  468.          (ispell-next-word)
  469.          (set-marker start (point))))))
  470.     ;;clear the choices buffer; otherwise it's hard for the user to tell
  471.     ;;when we get back to the command loop
  472.     (let ((buf (get-buffer "*ispell choices*")))
  473.       (cond (buf
  474.          (set-buffer buf)
  475.          (erase-buffer))))
  476.     (set-marker start nil)
  477.     (set-marker end nil)
  478.     (set-marker wend nil)))
  479.   
  480. (defun ispell-still-bad (word)
  481.   (let ((words ispell-recently-accepted)
  482.     (ret t)
  483.     (case-fold-search t))
  484.     (while words
  485.       (cond ((eq (string-match (car words) word) 0)
  486.          (setq ret nil)
  487.          (setq words nil)))
  488.       (setq words (cdr words)))
  489.     ret))
  490.  
  491. (defun ispell-show-choices (word message first-line)
  492.   ;;if there is only one window on the frame, make the ispell
  493.   ;;messages winow be small.  otherwise just use the other window
  494.   (let* ((selwin (selected-window))
  495.      (resize (eq selwin (next-window)))
  496.      (buf (get-buffer-create "*ispell choices*"))
  497.      w)
  498.     (or ispell-window-configuration
  499.     (setq ispell-window-configuration (current-window-configuration)))
  500.     (setq w (display-buffer buf))
  501.     (buffer-disable-undo buf)
  502.     (if resize
  503.     (unwind-protect
  504.         (progn
  505.           (select-window w)
  506.           (enlarge-window (- 6 (window-height w))))
  507.       (select-window selwin)))
  508.     (save-excursion
  509.       (set-buffer buf)
  510.       (bury-buffer buf)
  511.       (set-window-point w (point-min))
  512.       (set-window-start w (point-min))
  513.       (erase-buffer)
  514.       (insert first-line "\n")
  515.       (insert
  516.        "SPC skip; A accept; I insert; DIGIT select; R replace; \
  517. L lookup; Q quit\n")
  518.       (cond ((not (null message))
  519.          (let ((i 0))
  520.            (while (< i 3)
  521.          (let ((j 0))
  522.            (while (< j 3)
  523.              (let* ((n (+ (* j 3) i))
  524.                 (choice (nth n message)))
  525.                (cond (choice
  526.                   (let ((str (format "%d %s" n choice)))
  527.                 (insert str)
  528.                 (insert-char ?  (- 20 (length str)))))))
  529.              (setq j (+ j 1))))
  530.          (insert "\n")
  531.          (setq i (+ i 1)))))))))
  532.  
  533. (defun ispell-command-loop (word start end message)
  534.   (let ((flag t)
  535.     (rescan nil)
  536.     first-line)
  537.     (if (null message)
  538.     (setq first-line (concat "No near misses for '" word "'"))
  539.       (setq first-line (concat "Near misses for '" word "'")))
  540.     (ispell-highlight start end)
  541.     (while flag
  542.       (ispell-show-choices word message first-line)
  543.       (message "Ispell command: ")
  544.       (undo-boundary)
  545.       (let ((c (downcase (read-char)))
  546.         replacement)
  547.     (cond ((and (>= c ?0)
  548.             (<= c ?9)
  549.             (setq replacement (nth (- c ?0) message)))
  550.            (ispell-replace start end replacement)
  551.            (setq flag nil))
  552.           ((= c ?q)
  553.            (throw 'ispell-quit nil))
  554.           ((= c (nth 3 (current-input-mode)))
  555.            (keyboard-quit))
  556.           ((= c ? )
  557.            (setq flag nil))
  558.           ((= c ?r)
  559.            (ispell-replace start end (read-string "Replacement: "))
  560.            (setq rescan t)
  561.            (setq flag nil))
  562.           ((= c ?i)
  563.            (ispell-insert word)
  564.            (setq flag nil))
  565.           ((= c ?a)
  566.            (ispell-accept word)
  567.            (setq flag nil))
  568.           ((= c ?l)
  569.            (let ((val (ispell-do-look word)))
  570.          (setq first-line (car val))
  571.          (setq message (cdr val))))
  572.           ((= c ??)
  573.            (message
  574.         "Type 'C-h d ispell' to the emacs main loop for more help")
  575.            (sit-for 2))
  576.           (t
  577.            (message "Bad ispell command")
  578.            (sit-for 2)))))
  579.     rescan))
  580.  
  581. (defun ispell-do-look (bad-word)
  582.   (let (regex buf words)
  583.     (cond ((null ispell-have-new-look)
  584.        (setq regex (read-string "Lookup: ")))
  585.       (t
  586.        (setq regex (read-string "Lookup (regex): " "^"))))
  587.     (setq buf (get-buffer-create "*ispell look*"))
  588.     (save-excursion
  589.       (set-buffer buf)
  590.       (delete-region (point-min) (point-max))
  591.       (if ispell-have-new-look
  592.       (call-process ispell-look-command nil buf nil "-r" regex)
  593.     (call-process ispell-look-command nil buf nil regex))
  594.       (goto-char (point-min))
  595.       (forward-line 10)
  596.       (delete-region (point) (point-max))
  597.       (goto-char (point-min))
  598.       (while (not (= (point-min) (point-max)))
  599.     (end-of-line)
  600.     (setq words (cons (buffer-substring (point-min) (point)) words))
  601.     (forward-line)
  602.     (delete-region (point-min) (point)))
  603.       (kill-buffer buf)
  604.       (cons (format "Lookup '%s'" regex)
  605.         (reverse words)))))
  606.     
  607. (defun ispell-replace (start end new)
  608.   (goto-char start)
  609.   (insert new)
  610.   (delete-region (point) end))
  611.  
  612. (defun reload-ispell ()
  613.   "Tell Ispell to re-read your private dictionary."
  614.   (interactive)
  615.   (ispell-cmd ":reload"))
  616.  
  617. (defun batch-make-ispell ()
  618.   (byte-compile-file "ispell.el")
  619.   (find-file "ispell.texinfo")
  620.   (let ((old-dir default-directory)
  621.     (default-directory "/tmp"))
  622.     (texinfo-format-buffer))
  623.   (Info-validate)
  624.   (if (get-buffer " *problems in info file*")
  625.       (kill-emacs 1))
  626.   (write-region (point-min) (point-max) "ispell.info"))
  627.  
  628. (defvar ispell-highlight t
  629.   "*Non-nil means to highlight ispell words.")
  630.  
  631. (defvar ispell-overlay nil)
  632.  
  633. (defun ispell-dehighlight ()
  634.   (and ispell-overlay
  635.        (progn
  636.      (delete-overlay ispell-overlay)
  637.      (setq ispell-overlay nil))))
  638.  
  639. (defun ispell-highlight (start end)
  640.   (and ispell-highlight 
  641.        window-system
  642.        (progn
  643.      (or ispell-overlay
  644.          (progn
  645.            (setq ispell-overlay (make-overlay start end))
  646.            (overlay-put ispell-overlay 'face
  647.                 (if (internal-find-face 'ispell)
  648.                 'ispell 'region))))
  649.      (move-overlay ispell-overlay start end (current-buffer)))))
  650.  
  651. ;;;; ispell-complete-word
  652.  
  653. ;;; Brief Description:
  654. ;;; Complete word fragment at point using dictionary and replace with full
  655. ;;; word.  Expansion done in current buffer like lisp-complete-symbol.
  656. ;;; Completion of interior word fragments possible with prefix argument.
  657.  
  658. ;;; Known Problem: 
  659. ;;; Does not use private dictionary because GNU `look' does not use it.  It
  660. ;;; would be nice if GNU `look' took standard input; this would allow gzip'ed
  661. ;;; dictionaries to be used.  GNU `look' also has a bug, see
  662. ;;; `ispell-gnu-look-still-broken-p'.
  663.  
  664. ;;; Motivation: 
  665. ;;; The `l', "regular expression look up", keymap option of ispell-word
  666. ;;; (ispell-do-look) can only be run after finding a misspelled word.  So
  667. ;;; ispell-do-look can not be used to look for words starting with `cat' to
  668. ;;; find `catechetical' since `cat' is a correctly spelled word.  Furthermore,
  669. ;;; ispell-do-look does not return the entire list returned by `look'.
  670. ;;;  
  671. ;;; ispell-complete-word allows you to get a completion list from the system
  672. ;;; dictionary and expand a word fragment at the current position in a buffer.
  673. ;;; These examples assume ispell-complete-word is bound to M-TAB as it is in
  674. ;;; text-mode; the `Complete Word' and `Complete Interior Fragment' entries of
  675. ;;; the "Spell" submenu under the "Edit" menu may also be used instead of
  676. ;;; M-TAB and C-u M-TAB, respectively.
  677. ;;;
  678. ;;;   EXAMPLE 1: The word `Saskatchewan' needs to be spelled.  The user may
  679. ;;;   type `Sas' and hit M-TAB and a completion list will be built using the
  680. ;;;   shell command `look' and displayed in the *Completions* buffer:
  681. ;;;
  682. ;;;        Possible completions are:
  683. ;;;        sash                               sashay
  684. ;;;        sashayed                           sashed
  685. ;;;        sashes                             sashimi
  686. ;;;        Saskatchewan                       Saskatoon
  687. ;;;        sass                               sassafras
  688. ;;;        sassier                            sassing
  689. ;;;        sasswood                           sassy
  690. ;;;
  691. ;;;   By viewing this list the user will hopefully be motivated to insert the
  692. ;;;   letter `k' after the `sas'.  When M-TAB is hit again the word `Saskat'
  693. ;;;   will be inserted in place of `sas' (note case) since this is a unique
  694. ;;;   substring completion.  The narrowed completion list can be viewed with
  695. ;;;   another M-TAB
  696. ;;;
  697. ;;;        Possible completions are:
  698. ;;;        Saskatchewan                       Saskatoon
  699. ;;;
  700. ;;;   Inserting the letter `c' and hitting M-TAB will narrow the completion
  701. ;;;   possibilities to just `Saskatchewan' and this will be inserted in the
  702. ;;;   buffer.  At any point the user may click the mouse on a completion to
  703. ;;;   select it.
  704. ;;;
  705. ;;;   EXAMPLE 2: The user has typed `Sasaquane' and M-$ (ispell-word) gives no
  706. ;;;   "near-misses" in which case you back up to `Sas' and hit M-TAB and find
  707. ;;;   the correct word as above.  The `Sas' will be replaced by `Saskatchewan'
  708. ;;;   and the remaining word fragment `aquane' can be deleted.
  709. ;;;
  710. ;;;   EXAMPLE 3: If a version of `look' is used that supports regular
  711. ;;;   expressions, then `ispell-have-new-look' should be t (its default) and
  712. ;;;   interior word fragments may also be used for the search.  The word
  713. ;;;   `pneumonia' needs to be spelled.  The user can only remember the
  714. ;;;   interior fragment `mon' in which case `C-u M-TAB' on `mon' gives a list
  715. ;;;   of all words containing the interior word fragment `mon'.  Typing `p'
  716. ;;;   and M-TAB will narrow this list to all the words starting with `p' and
  717. ;;;   containing `mon' from which `pneumonia' can be found as above.
  718.  
  719. ;;; The user-defined variables are:
  720. ;;;
  721. ;;;  ispell-look-command
  722. ;;;  ispell-look-dictionary
  723. ;;;  ispell-gnu-look-still-broken-p
  724.  
  725. ;;; Algorithm (some similarity to lisp-complete-symbol):
  726. ;;;  
  727. ;;; * call-process on command ispell-look-command (default: "look") to find
  728. ;;;   words in ispell-look-dictionary matching `string' (or `regexp' if 
  729. ;;;   ispell-have-new-look is t).  Parse output and store results in 
  730. ;;;   ispell-lookup-completions-alist.
  731. ;;; 
  732. ;;; * Build completion list using try-completion and `string'
  733. ;;; 
  734. ;;; * Replace `string' in buffer with matched common substring completion.
  735. ;;; 
  736. ;;; * Display completion list only if there is no matched common substring.
  737. ;;; 
  738. ;;; * Rebuild ispell-lookup-completions-alist, on a next call, only when
  739. ;;;   beginning of word fragment has changed.
  740. ;;;  
  741. ;;; * Interior fragments searches are performed similarly with the exception
  742. ;;;   that the entire fragment at point is initially removed from the buffer,
  743. ;;;   the STRING passed to try-completion and all-completions is just "" and
  744. ;;;   not the interior fragment; this allows all completions containing the
  745. ;;;   interior fragment to be shown.  The location in the buffer is stored to
  746. ;;;   decide whether future completion narrowing of the current list should be
  747. ;;;   done or if a new list should be built.  See interior fragment example
  748. ;;;   above.
  749. ;;;
  750. ;;; * Robust searches are done using a `look' with -r (regular expression) 
  751. ;;;   switch if ispell-have-new-look is t.
  752.  
  753. ;;;; User-defined variables.
  754.  
  755. (defvar ispell-look-dictionary nil
  756.   "*If non-nil then spelling dictionary as string for `ispell-complete-word'.
  757. Overrides default dictionary file such as \"/usr/dict/words\" or GNU look's
  758. \"${prefix}/lib/ispell/ispell.words\"")
  759.  
  760. (defvar ispell-gnu-look-still-broken-p nil
  761.   "*t if GNU look -r can give different results with and without trialing `.*'.
  762. Example: `look -dfr \"^ya\" foo' returns nothing, while `look -dfr \"^ya.*\" foo'
  763. returns `yacc', where `foo' is a dictionary file containing the three lines
  764.  
  765.    y
  766.    y's
  767.    yacc
  768.  
  769. Both commands should return `yacc'.  If `ispell-complete-word' erroneously
  770. states that no completions exist for a string, then setting this variable to t
  771. will help find those completions.")
  772.  
  773. ;;;; Internal variables.
  774.  
  775. ;;; Possible completions for last word fragment.
  776. (defvar ispell-lookup-completions-alist nil)
  777.  
  778. ;;; Last word fragment processed by `ispell-complete-word'.
  779. (defvar ispell-lookup-last-word nil)
  780.  
  781. ;;; Buffer local variables.
  782.  
  783. ;;; Value of interior-frag in last call to `ispell-complete-word'.
  784. (defvar ispell-lookup-last-interior-p nil)
  785. (make-variable-buffer-local 'ispell-lookup-last-interior-p)
  786. (put 'ispell-lookup-last-interior-p 'permanent-local t)
  787.  
  788. ;;; Buffer position in last call to `ispell-complete-word'.
  789. (defvar ispell-lookup-last-bow nil)
  790. (make-variable-buffer-local 'ispell-lookup-last-bow)
  791. (put 'ispell-lookup-last-bow 'permanent-local t)
  792.  
  793. ;;;; Interactive functions.
  794. ;;;###autoload
  795. (defun ispell-complete-word (&optional interior-frag)
  796.   "Complete word using letters at point to word beginning using `look'.
  797. With optional argument INTERIOR-FRAG, word fragment at point is assumed to be
  798. an interior word fragment in which case `ispell-have-new-look' should be t.
  799. See also `ispell-look-dictionary' and `ispell-gnu-look-still-broken-p'."
  800.  
  801.   (interactive "P")
  802.  
  803.   ;; `look' must support regexp expressions in order to perform an interior
  804.   ;; fragment search.
  805.   (if (and interior-frag (not ispell-have-new-look))
  806.       (error (concat "Sorry, `ispell-have-new-look' is nil.  "
  807.                      "You also will need GNU Ispell's `look'.")))
  808.  
  809.   (let* ((completion-ignore-case t)
  810.  
  811.          ;; Get location of beginning of word fragment.
  812.          (bow (save-excursion (skip-chars-backward "a-zA-Z'") (point)))
  813.  
  814.          ;; Get the string to look up.
  815.          (string (buffer-substring bow (point)))
  816.  
  817.          ;; Get regexp for which we search and, if necessary, an interior word
  818.          ;; fragment.
  819.          (regexp (if interior-frag
  820.                      (concat "^.*" string ".*")
  821.                    ;; If possible use fast binary search: no trailing `.*'.
  822.                    (concat "^" string
  823.                            (if ispell-gnu-look-still-broken-p ".*"))))
  824.  
  825.          ;; We want all completions for case of interior fragments so set
  826.          ;; prefix to an empty string.
  827.          (prefix (if interior-frag "" string))
  828.  
  829.          ;; Are we continuing from a previous interior fragment search?
  830.          ;; Check last value of interior-word and if the point has moved.
  831.          (continuing-an-interior-frag-p
  832.           (and ispell-lookup-last-interior-p
  833.                (equal ispell-lookup-last-bow bow)))
  834.  
  835.          ;; Are we starting a unique word fragment search?  Always t for
  836.          ;; interior word fragment search.
  837.          (new-unique-string-p
  838.           (or interior-frag (null ispell-lookup-last-word)
  839.               (let ((case-fold-search t))
  840.                 ;; Can we locate last word fragment as a substring of current
  841.                 ;; word fragment?  If the last word fragment is larger than
  842.                 ;; the current string then we will have to rebuild the list
  843.                 ;; later.
  844.                 (not (string-match
  845.                       (concat "^" ispell-lookup-last-word) string)))))
  846.  
  847.          completion)
  848.  
  849.     ;; Check for perfect completion already.  That is, maybe the user has hit
  850.     ;; M-x ispell-complete-word one too many times?
  851.     (if (string-equal string "")
  852.         (if (string-equal (concat ispell-lookup-last-word " ")
  853.                           (buffer-substring
  854.                            (save-excursion (forward-word -1) (point)) (point)))
  855.             (error "Perfect match already")
  856.           (error "No word fragment at point")))
  857.  
  858.     ;; Create list of words from system dictionary starting with `string' if
  859.     ;; new string and not continuing from a previous interior fragment search.
  860.     (if (and (not continuing-an-interior-frag-p) new-unique-string-p)
  861.         (setq ispell-lookup-completions-alist
  862.               (ispell-lookup-build-list string regexp)))
  863.  
  864.     ;; Check for a completion of `string' in the list and store `string' and
  865.     ;; other variables for the next call.
  866.     (setq completion (try-completion prefix ispell-lookup-completions-alist)
  867.           ispell-lookup-last-word string
  868.           ispell-lookup-last-interior-p interior-frag
  869.           ispell-lookup-last-bow bow)
  870.  
  871.     ;; Test the completion status.
  872.     (cond
  873.  
  874.      ;; * Guess is a perfect match.
  875.      ((eq completion t)
  876.       (insert " ")
  877.       (message "Perfect match."))
  878.  
  879.      ;; * No possibilities.
  880.      ((null completion)
  881.       (message "Can't find completion for \"%s\"" string)
  882.       (beep))
  883.  
  884.      ;; * Replace string fragment with matched common substring completion.
  885.      ((and (not (string-equal completion ""))
  886.            ;; Fold case so a completion list is built when `string' and common
  887.            ;; substring differ only in case.
  888.            (let ((case-fold-search t))
  889.              (not (string-match (concat "^" completion "$") string))))
  890.       (search-backward string bow)
  891.       (replace-match completion nil t) ; FIXEDCASE doesn't work? or LITERAL?
  892.       (message "Proposed unique substring.  Repeat for completions list."))
  893.  
  894.      ;; * String is a common substring completion already.  Make list.
  895.      (t
  896.       (message "Making completion list...")
  897.       (if (string-equal completion "") (delete-region bow (point)))
  898.       (let ((list (all-completions prefix ispell-lookup-completions-alist)))
  899.         (with-output-to-temp-buffer " *Completions*"
  900.           (display-completion-list list)))
  901.       (message "Making completion list...done")))))
  902.  
  903. ;;;###autoload
  904. (defun ispell-complete-word-interior-frag ()
  905.   "Runs `ispell-complete-word' with a non-nil INTERIOR-FRAG.
  906. A completion list is built for word fragment at point which is assumed to be
  907. an interior word fragment.  `ispell-have-new-look' should be t."
  908.   (interactive)
  909.   (ispell-complete-word t))
  910.  
  911. ;;;; Internal Function.
  912.  
  913. ;;; Build list of words using ispell-look-command from dictionary
  914. ;;; ispell-look-dictionary (if this is a non-nil string).  Look for words
  915. ;;; starting with STRING if ispell-have-new-look is nil or look for REGEXP if
  916. ;;; ispell-have-new-look is t.  Returns result as an alist suitable for use by
  917. ;;; try-completion, all-completions, and completing-read.
  918. (defun ispell-lookup-build-list (string regexp)
  919.   (save-excursion
  920.     (message "Building list...")
  921.     (set-buffer (get-buffer-create " *ispell look*"))
  922.     (erase-buffer)
  923.  
  924.     (if (stringp ispell-look-dictionary)
  925.         (if ispell-have-new-look
  926.             (call-process ispell-look-command nil t nil "-fr" regexp
  927.                           ispell-look-dictionary)
  928.           (call-process ispell-look-command nil t nil "-f" string
  929.                         ispell-look-dictionary))
  930.       (if ispell-have-new-look
  931.           (call-process ispell-look-command nil t nil "-fr" regexp)
  932.         (call-process ispell-look-command nil t nil "-f" string)))
  933.  
  934.     ;; Build list for try-completion and all-completions by storing each line
  935.     ;; of output starting from bottom of buffer and deleting upwards.
  936.     (let (list)
  937.       (goto-char (point-min))
  938.       (while (not (= (point-min) (point-max)))
  939.         (end-of-line)
  940.         (setq list (cons (buffer-substring (point-min) (point)) list))
  941.         (forward-line)
  942.         (delete-region (point-min) (point)))
  943.  
  944.       ;; Clean.
  945.       (erase-buffer)
  946.       (message "Building list...done")
  947.  
  948.       ;; Make the list into an alist and return.
  949.       (mapcar 'list (nreverse list)))))
  950.  
  951. ;; Return regexp-quote of STRING if STRING is non-empty.
  952. ;; Otherwise return an unmatchable regexp.
  953. (defun ispell-non-empty-string (string)
  954.   (if (or (not string) (string-equal string ""))
  955.       "\\'\\`" ; An unmatchable string if string is null.
  956.     (regexp-quote string)))
  957.  
  958. (defvar ispell-message-cite-regexp "^   \\|^\t"
  959.   "*Regular expression to match lines cited from one message into another.")
  960.  
  961. (defvar ispell-message-text-end
  962.   (concat "^\\(" (mapconcat (function identity)
  963.                 '(
  964.                   ;; Matches postscript files.
  965.                   "%!PS-Adobe-2.0"
  966.                   ;; Matches uuencoded text
  967.                   "begin [0-9][0-9][0-9] .*\nM.*\nM.*\nM"
  968.                   ;; Matches shell files (esp. auto-decoding)
  969.                   "#! /bin/sh"
  970.                   ;; Matches difference listing
  971.                   "diff -c .*\n\\*\\*\\* .*\n--- "
  972.                   ;; Matches "--------------------- cut here"
  973.                   "[-=]+\\s cut here")
  974.                 "\\|")
  975.           "\\)")
  976.   "*End of text which will be checked in ispell-message.
  977. If it is a string, limit at first occurence of that regular expression.
  978. Otherwise, it must be a function which is called to get the limit.")
  979.  
  980. (defvar ispell-message-limit (* 100 80)
  981.   "*Ispell-message will check no more than this number of characters.")
  982.  
  983. ;;;###autoload
  984. (defun ispell-message ()
  985.   "Check the spelling of a mail message or news post.
  986. Don't check spelling of message headers (except subject) or included messages.
  987.  
  988. To spell-check whenever a message is sent, include this line in .emacs:
  989.    (setq news-inews-hook (setq mail-send-hook 'ispell-message))
  990.  
  991. Or you can bind the function to C-c i in gnus or mail with:
  992.    (setq mail-mode-hook (setq news-reply-mode-hook
  993.     (function (lambda () (local-set-key \"\\C-ci\" 'ispell-message)))))"
  994.   (interactive)
  995.   (save-excursion
  996.     (let (non-internal-message
  997.       (old-case-fold-search case-fold-search)
  998.       (case-fold-search nil))
  999.       (goto-char (point-min))
  1000.       ;; Don't spell-check the headers.
  1001.       (if (search-forward mail-header-separator nil t)
  1002.       ;; Move to first body line.
  1003.       (forward-line 1)
  1004.     (while (and (looking-at "[a-zA-Z-]+:\\|\t\\| ")
  1005.             (not (eobp)))
  1006.       (forward-line 1))
  1007.     (setq non-internal-message t)
  1008.     )
  1009.       (let* ((cite-regexp        ;Prefix of inserted text
  1010.          (cond
  1011.           ((featurep 'supercite)    ; sc 3.0
  1012.            (concat "\\(" (sc-cite-regexp) "\\)" "\\|"
  1013.                (ispell-non-empty-string sc-reference-tag-string)))
  1014.           ((featurep 'sc)        ; sc 2.3
  1015.            (concat "\\(" sc-cite-regexp "\\)" "\\|"
  1016.                (ispell-non-empty-string sc-reference-tag-string)))
  1017.           (non-internal-message    ; Assume nn sent us this message.
  1018.            (concat "In [a-zA-Z.]+ you write:" "\\|"
  1019.                "In <[^,;&+=]+> [^,;&+=]+ writes:" "\\|"
  1020.                " *> *"))
  1021.           ((equal major-mode 'news-reply-mode) ;Gnus
  1022.            (concat "In article <" "\\|"
  1023.                (if mail-yank-prefix
  1024.                (ispell-non-empty-string mail-yank-prefix)
  1025.              ispell-message-cite-regexp)))
  1026.           ((boundp 'vm-included-text-prefix) ; VM mail message
  1027.            (concat "[^,;&+=]+ writes:" "\\|"
  1028.                (ispell-non-empty-string vm-included-text-prefix)
  1029.                ))
  1030.           ((boundp 'mh-ins-buf-prefix) ; mh mail message
  1031.            (ispell-non-empty-string mh-ins-buf-prefix))
  1032.           (mail-yank-prefix            ; vanilla mail message.
  1033.            (ispell-non-empty-string mail-yank-prefix))
  1034.           (t ispell-message-cite-regexp)))
  1035.         (continue t)
  1036.         (limit
  1037.          (min
  1038.           (+ (point-min) ispell-message-limit)
  1039.           (point-max)
  1040.           (save-excursion
  1041.          (cond
  1042.           ((not ispell-message-text-end) (point-max))
  1043.           ((char-or-string-p ispell-message-text-end)
  1044.            (if (re-search-forward ispell-message-text-end nil 'end)
  1045.                (match-beginning 0)
  1046.              (point-max)))
  1047.           (t (funcall ispell-message-text-end))))))
  1048.         (search-limit ; Search limit which won't stop in middle of citation
  1049.          (+ limit (length cite-regexp)))
  1050.         )
  1051.      ;; Check the subject
  1052.      (save-excursion
  1053.        (let ((case-fold-search t)
  1054.          (message-begin (point)))
  1055.          (goto-char (point-min))
  1056.          ;; "\\s *" matches newline if subject is empty
  1057.          (if (and (re-search-forward "^Subject:[\t ]*" message-begin t)
  1058.               (not (looking-at "re\\>")))
  1059.          (setq continue
  1060.                (ispell-region (- (point) 1)
  1061.                       (progn
  1062.                        (end-of-line)
  1063.                        (while (looking-at "\n[ \t]")
  1064.                      (end-of-line 2))
  1065.                        (point))))
  1066.            )))
  1067.  
  1068.     ;; Check the body.
  1069.     (while (and (< (point) limit) continue)
  1070.       ;; Skip across text cited from other messages.
  1071.       (while (and (looking-at (concat "^[ \t]*$\\|" cite-regexp))
  1072.               (< (point) limit))
  1073.         (forward-line 1))
  1074.       (if (< (point) limit)
  1075.           ;; Check the next batch of lines that *aren't* cited.
  1076.           (let ((start (point)))
  1077.         (if (re-search-forward
  1078.              (concat "^\\(" cite-regexp "\\)") search-limit 'end)
  1079.             (beginning-of-line))
  1080.         (if (> (point) limit) (goto-char limit))
  1081.         (let ((case-fold-search old-case-fold-search))
  1082.           (save-excursion
  1083.             (setq continue (ispell-region (- start 1) (point))))))))))))
  1084.  
  1085. (provide 'ispell)
  1086.  
  1087. ;;; ispell.el ends here
  1088.