| 1 | ;;; -*- Log: hemlock.log; Package: Hemlock -*-
|
|---|
| 2 | ;;;
|
|---|
| 3 | ;;; **********************************************************************
|
|---|
| 4 | ;;; This code was written as part of the CMU Common Lisp project at
|
|---|
| 5 | ;;; Carnegie Mellon University, and has been placed in the public domain.
|
|---|
| 6 | ;;;
|
|---|
| 7 | #+CMU (ext:file-comment
|
|---|
| 8 | "$Header$")
|
|---|
| 9 | ;;;
|
|---|
| 10 | ;;; **********************************************************************
|
|---|
| 11 | ;;;
|
|---|
| 12 | ;;; Written by Bill Chiles and Rob Maclachlan.
|
|---|
| 13 | ;;;
|
|---|
| 14 | ;;; This file contains the code to implement commands using the spelling
|
|---|
| 15 | ;;; checking/correcting stuff in Spell-Corr.Lisp and the dictionary
|
|---|
| 16 | ;;; augmenting stuff in Spell-Augment.Lisp.
|
|---|
| 17 |
|
|---|
| 18 | (in-package :hemlock)
|
|---|
| 19 |
|
|---|
| 20 |
|
|---|
| 21 |
|
|---|
| 22 | (defstruct (spell-info (:print-function print-spell-info)
|
|---|
| 23 | (:constructor make-spell-info (pathname)))
|
|---|
| 24 | pathname ;Dictionary file.
|
|---|
| 25 | insertions) ;Incremental insertions for this dictionary.
|
|---|
| 26 |
|
|---|
| 27 | (defun print-spell-info (obj str n)
|
|---|
| 28 | (declare (ignore n))
|
|---|
| 29 | (let ((pn (spell-info-pathname obj)))
|
|---|
| 30 | (format str "#<Spell Info~@[ ~S~]>"
|
|---|
| 31 | (and pn (namestring pn)))))
|
|---|
| 32 |
|
|---|
| 33 |
|
|---|
| 34 | (defattribute "Spell Word Character"
|
|---|
| 35 | "One if the character is one that is present in the spell dictionary,
|
|---|
| 36 | zero otherwise.")
|
|---|
| 37 |
|
|---|
| 38 | (do-alpha-chars (c :both)
|
|---|
| 39 | (setf (character-attribute :spell-word-character c) 1))
|
|---|
| 40 | (setf (character-attribute :spell-word-character #\') 1)
|
|---|
| 41 |
|
|---|
| 42 |
|
|---|
| 43 | (defvar *spelling-corrections* (make-hash-table :test #'equal)
|
|---|
| 44 | "Mapping from incorrect words to their corrections.")
|
|---|
| 45 |
|
|---|
| 46 | (defvar *ignored-misspellings* (make-hash-table :test #'equal)
|
|---|
| 47 | "A hashtable with true values for words that will be quietly ignored when
|
|---|
| 48 | they appear.")
|
|---|
| 49 |
|
|---|
| 50 | (defhvar "Spell Ignore Uppercase"
|
|---|
| 51 | "If true, then \"Check Word Spelling\" and \"Correct Buffer Spelling\" will
|
|---|
| 52 | ignore unknown words that are all uppercase. This is useful for
|
|---|
| 53 | abbreviations and cryptic formatter directives."
|
|---|
| 54 | :value nil)
|
|---|
| 55 |
|
|---|
| 56 |
|
|---|
| 57 | |
|---|
| 58 |
|
|---|
| 59 | ;;;; Basic Spelling Correction Command (Esc-$ in EMACS)
|
|---|
| 60 |
|
|---|
| 61 | (defcommand "Check Word Spelling" (p)
|
|---|
| 62 | "Check the spelling of the previous word and offer possible corrections
|
|---|
| 63 | if the word in unknown. To add words to the dictionary from a text file see
|
|---|
| 64 | the command \"Augment Spelling Dictionary\"."
|
|---|
| 65 | "Check the spelling of the previous word and offer possible correct
|
|---|
| 66 | spellings if the word is known to be misspelled."
|
|---|
| 67 | (declare (ignore p))
|
|---|
| 68 | (spell:maybe-read-spell-dictionary)
|
|---|
| 69 | (let* ((region (spell-previous-word (current-point) nil))
|
|---|
| 70 | (word (if region
|
|---|
| 71 | (region-to-string region)
|
|---|
| 72 | (editor-error "No previous word.")))
|
|---|
| 73 | (folded (string-upcase word)))
|
|---|
| 74 | (message "Checking spelling of ~A." word)
|
|---|
| 75 | (unless (check-out-word-spelling word folded)
|
|---|
| 76 | (get-word-correction (region-start region) word folded))))
|
|---|
| 77 |
|
|---|
| 78 | |
|---|
| 79 |
|
|---|
| 80 | ;;;; Auto-Spell mode:
|
|---|
| 81 |
|
|---|
| 82 | (defhvar "Check Word Spelling Beep"
|
|---|
| 83 | "If true, \"Auto Check Word Spelling\" will beep when an unknown word is
|
|---|
| 84 | found."
|
|---|
| 85 | :value t)
|
|---|
| 86 |
|
|---|
| 87 | (defhvar "Correct Unique Spelling Immediately"
|
|---|
| 88 | "If true, \"Auto Check Word Spelling\" will immediately attempt to correct any
|
|---|
| 89 | unknown word, automatically making the correction if there is only one
|
|---|
| 90 | possible."
|
|---|
| 91 | :value t)
|
|---|
| 92 |
|
|---|
| 93 |
|
|---|
| 94 | (defhvar "Default User Spelling Dictionary"
|
|---|
| 95 | "This is the pathname of a dictionary to read the first time \"Spell\" mode
|
|---|
| 96 | is entered in a given editing session. When \"Set Buffer Spelling
|
|---|
| 97 | Dictionary\" or the \"dictionary\" file option is used to specify a
|
|---|
| 98 | dictionary, this default one is read also. It defaults to nil."
|
|---|
| 99 | :value nil)
|
|---|
| 100 |
|
|---|
| 101 | (defvar *default-user-dictionary-read-p* nil)
|
|---|
| 102 |
|
|---|
| 103 | (defun maybe-read-default-user-spelling-dictionary ()
|
|---|
| 104 | (let ((default-dict (value default-user-spelling-dictionary)))
|
|---|
| 105 | (when (and default-dict (not *default-user-dictionary-read-p*))
|
|---|
| 106 | (spell:maybe-read-spell-dictionary)
|
|---|
| 107 | (spell:spell-read-dictionary (truename default-dict))
|
|---|
| 108 | (setf *default-user-dictionary-read-p* t))))
|
|---|
| 109 |
|
|---|
| 110 |
|
|---|
| 111 | (defmode "Spell"
|
|---|
| 112 | :transparent-p t :precedence 1.0 :setup-function 'spell-mode-setup)
|
|---|
| 113 |
|
|---|
| 114 | (defun spell-mode-setup (buffer)
|
|---|
| 115 | (defhvar "Buffer Misspelled Words"
|
|---|
| 116 | "This variable holds a ring of marks pointing to misspelled words."
|
|---|
| 117 | :buffer buffer :value (make-ring 10 #'delete-mark))
|
|---|
| 118 | (maybe-read-default-user-spelling-dictionary))
|
|---|
| 119 |
|
|---|
| 120 | (defcommand "Auto Spell Mode" (p)
|
|---|
| 121 | "Toggle \"Spell\" mode in the current buffer. When in \"Spell\" mode,
|
|---|
| 122 | the spelling of each word is checked after it is typed."
|
|---|
| 123 | "Toggle \"Spell\" mode in the current buffer."
|
|---|
| 124 | (declare (ignore p))
|
|---|
| 125 | (setf (buffer-minor-mode (current-buffer) "Spell")
|
|---|
| 126 | (not (buffer-minor-mode (current-buffer) "Spell"))))
|
|---|
| 127 |
|
|---|
| 128 |
|
|---|
| 129 | (defcommand "Auto Check Word Spelling" (p)
|
|---|
| 130 | "Check the spelling of the previous word and display a message in the echo
|
|---|
| 131 | area if the word is not in the dictionary. To add words to the dictionary
|
|---|
| 132 | from a text file see the command \"Augment Spelling Dictionary\". If a
|
|---|
| 133 | replacement for an unknown word has previously been specified, then the
|
|---|
| 134 | replacement will be made immediately. If \"Correct Unique Spelling
|
|---|
| 135 | Immediately\" is true, then this command will immediately correct words
|
|---|
| 136 | which have a unique correction. If there is no obvious correction, then we
|
|---|
| 137 | place the word in a ring buffer for access by the \"Correct Last Misspelled
|
|---|
| 138 | Word\" command. If \"Check Word Spelling Beep\" is true, then this command
|
|---|
| 139 | beeps when an unknown word is found, in addition to displaying the message."
|
|---|
| 140 | "Check the spelling of the previous word, making obvious corrections, or
|
|---|
| 141 | queuing the word in buffer-misspelled-words if we are at a loss."
|
|---|
| 142 | (declare (ignore p))
|
|---|
| 143 | (unless (eq (last-command-type) :spell-check)
|
|---|
| 144 | (spell:maybe-read-spell-dictionary)
|
|---|
| 145 | (let ((region (spell-previous-word (current-point) t)))
|
|---|
| 146 | (when region
|
|---|
| 147 | (let* ((word (nstring-upcase (region-to-string region)))
|
|---|
| 148 | (len (length word)))
|
|---|
| 149 | (declare (simple-string word))
|
|---|
| 150 | (when (and (<= 2 len spell:max-entry-length)
|
|---|
| 151 | (not (spell:spell-try-word word len)))
|
|---|
| 152 | (let ((found (gethash word *spelling-corrections*))
|
|---|
| 153 | (save (region-to-string region)))
|
|---|
| 154 | (cond (found
|
|---|
| 155 | (undoable-replace-word (region-start region) save found)
|
|---|
| 156 | (message "Corrected ~S to ~S." save found)
|
|---|
| 157 | (when (value check-word-spelling-beep) (beep)))
|
|---|
| 158 | ((and (value spell-ignore-uppercase)
|
|---|
| 159 | (every #'upper-case-p save))
|
|---|
| 160 | (unless (gethash word *ignored-misspellings*)
|
|---|
| 161 | (setf (gethash word *ignored-misspellings*) t)
|
|---|
| 162 | (message "Ignoring ~S." save)))
|
|---|
| 163 | (t
|
|---|
| 164 | (let ((close (spell:spell-collect-close-words word)))
|
|---|
| 165 | (cond ((and close
|
|---|
| 166 | (null (rest close))
|
|---|
| 167 | (value correct-unique-spelling-immediately))
|
|---|
| 168 | (let ((fix (first close)))
|
|---|
| 169 | (undoable-replace-word (region-start region)
|
|---|
| 170 | save fix)
|
|---|
| 171 | (message "Corrected ~S to ~S." save fix)))
|
|---|
| 172 | (t
|
|---|
| 173 | (ring-push (copy-mark (region-end region)
|
|---|
| 174 | :right-inserting)
|
|---|
| 175 | (value buffer-misspelled-words))
|
|---|
| 176 | (let ((nclose
|
|---|
| 177 | (do ((i 0 (1+ i))
|
|---|
| 178 | (words close (cdr words))
|
|---|
| 179 | (nwords () (cons (list i (car words))
|
|---|
| 180 | nwords)))
|
|---|
| 181 | ((null words) (nreverse nwords)))))
|
|---|
| 182 | (message
|
|---|
| 183 | "Word ~S not found.~
|
|---|
| 184 | ~@[ Corrections:~:{ ~D=~A~}~]"
|
|---|
| 185 | save nclose)))))
|
|---|
| 186 | (when (value check-word-spelling-beep) (beep))))))))))
|
|---|
| 187 | (setf (last-command-type) :spell-check))
|
|---|
| 188 |
|
|---|
| 189 | (defcommand "Correct Last Misspelled Word" (p)
|
|---|
| 190 | "Fix a misspelling found by \"Auto Check Word Spelling\". This prompts for
|
|---|
| 191 | a single character command to determine which action to take to correct the
|
|---|
| 192 | problem."
|
|---|
| 193 | "Prompt for a single character command to determine how to fix up a
|
|---|
| 194 | misspelling detected by Check-Word-Spelling-Command."
|
|---|
| 195 | (declare (ignore p))
|
|---|
| 196 | (spell:maybe-read-spell-dictionary)
|
|---|
| 197 | (do ((info (value spell-information)))
|
|---|
| 198 | ((sub-correct-last-misspelled-word info))))
|
|---|
| 199 |
|
|---|
| 200 | (defun sub-correct-last-misspelled-word (info)
|
|---|
| 201 | (let* ((missed (value buffer-misspelled-words))
|
|---|
| 202 | (region (cond ((zerop (ring-length missed))
|
|---|
| 203 | (editor-error "No recently misspelled word."))
|
|---|
| 204 | ((spell-previous-word (ring-ref missed 0) t))
|
|---|
| 205 | (t (editor-error "No recently misspelled word."))))
|
|---|
| 206 | (word (region-to-string region))
|
|---|
| 207 | (folded (string-upcase word))
|
|---|
| 208 | (point (current-point))
|
|---|
| 209 | (save (copy-mark point))
|
|---|
| 210 | (res t))
|
|---|
| 211 | (declare (simple-string word))
|
|---|
| 212 | (unwind-protect
|
|---|
| 213 | (progn
|
|---|
| 214 | (when (check-out-word-spelling word folded)
|
|---|
| 215 | (delete-mark (ring-pop missed))
|
|---|
| 216 | (return-from sub-correct-last-misspelled-word t))
|
|---|
| 217 | (move-mark point (region-end region))
|
|---|
| 218 | (command-case (:prompt "Action: "
|
|---|
| 219 | :change-window nil
|
|---|
| 220 | :help "Type a single character command to do something to the misspelled word.")
|
|---|
| 221 | (#\c "Try to find a correction for this word."
|
|---|
| 222 | (unless (get-word-correction (region-start region) word folded)
|
|---|
| 223 | (reprompt)))
|
|---|
| 224 | (#\i "Insert this word in the dictionary."
|
|---|
| 225 | (spell:spell-add-entry folded)
|
|---|
| 226 | (push folded (spell-info-insertions info))
|
|---|
| 227 | (message "~A inserted in the dictionary." word))
|
|---|
| 228 | (#\r "Prompt for a word to replace this word with."
|
|---|
| 229 | (let ((s (prompt-for-string :prompt "Replace with: "
|
|---|
| 230 | :default word
|
|---|
| 231 | :help "Type a string to replace occurrences of this word with.")))
|
|---|
| 232 | (delete-region region)
|
|---|
| 233 | (insert-string point s)
|
|---|
| 234 | (setf (gethash folded *spelling-corrections*) s)))
|
|---|
| 235 | (:cancel "Ignore this word and go to the previous misspelled word."
|
|---|
| 236 | (setq res nil))
|
|---|
| 237 | (:recursive-edit
|
|---|
| 238 | "Go into a recursive edit and leave when it exits."
|
|---|
| 239 | (do-recursive-edit))
|
|---|
| 240 | ((:exit #\q) "Exit and forget about this word.")
|
|---|
| 241 | ((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9)
|
|---|
| 242 | "Choose this numbered word as the correct spelling."
|
|---|
| 243 | (let ((num (digit-char-p (ext:key-event-char *last-key-event-typed*)))
|
|---|
| 244 | (close-words (spell:spell-collect-close-words folded)))
|
|---|
| 245 | (cond ((> num (length close-words))
|
|---|
| 246 | (editor-error "Choice out of range."))
|
|---|
| 247 | (t (let ((s (nth num close-words)))
|
|---|
| 248 | (setf (gethash folded *spelling-corrections*) s)
|
|---|
| 249 | (undoable-replace-word (region-start region)
|
|---|
| 250 | word s)))))))
|
|---|
| 251 | (delete-mark (ring-pop missed))
|
|---|
| 252 | res)
|
|---|
| 253 | (move-mark point save)
|
|---|
| 254 | (delete-mark save))))
|
|---|
| 255 |
|
|---|
| 256 | (defhvar "Spelling Un-Correct Prompt for Insert"
|
|---|
| 257 | "When this is set, \"Undo Last Spelling Correction\" will prompt before
|
|---|
| 258 | inserting the old word into the dictionary."
|
|---|
| 259 | :value nil)
|
|---|
| 260 |
|
|---|
| 261 | (defcommand "Undo Last Spelling Correction" (p)
|
|---|
| 262 | "Undo the last incremental spelling correction.
|
|---|
| 263 | The \"correction\" is replaced with the old word, and the old word is
|
|---|
| 264 | inserted in the dictionary. When \"Spelling Un-Correct Prompt for Insert\"
|
|---|
| 265 | is set, the user is asked about inserting the old word. Any automatic
|
|---|
| 266 | replacement for the old word is eliminated."
|
|---|
| 267 | "Undo the last incremental spelling correction, nuking any undesirable
|
|---|
| 268 | side-effects."
|
|---|
| 269 | (declare (ignore p))
|
|---|
| 270 | (unless (hemlock-bound-p 'last-spelling-correction-mark)
|
|---|
| 271 | (editor-error "No last spelling correction."))
|
|---|
| 272 | (let ((mark (value last-spelling-correction-mark))
|
|---|
| 273 | (words (value last-spelling-correction-words)))
|
|---|
| 274 | (unless words
|
|---|
| 275 | (editor-error "No last spelling correction."))
|
|---|
| 276 | (let* ((new (car words))
|
|---|
| 277 | (old (cdr words))
|
|---|
| 278 | (folded (string-upcase old)))
|
|---|
| 279 | (declare (simple-string old new folded))
|
|---|
| 280 | (remhash folded *spelling-corrections*)
|
|---|
| 281 | (delete-characters mark (length new))
|
|---|
| 282 | (insert-string mark old)
|
|---|
| 283 | (setf (value last-spelling-correction-words) nil)
|
|---|
| 284 | (when (or (not (value spelling-un-correct-prompt-for-insert))
|
|---|
| 285 | (prompt-for-y-or-n
|
|---|
| 286 | :prompt (list "Insert ~A into spelling dictionary? " folded)
|
|---|
| 287 | :default t
|
|---|
| 288 | :default-string "Y"))
|
|---|
| 289 | (push folded (spell-info-insertions (value spell-information)))
|
|---|
| 290 | (spell:maybe-read-spell-dictionary)
|
|---|
| 291 | (spell:spell-add-entry folded)
|
|---|
| 292 | (message "Added ~S to spelling dictionary." old)))))
|
|---|
| 293 |
|
|---|
| 294 |
|
|---|
| 295 | ;;; Check-Out-Word-Spelling -- Internal
|
|---|
| 296 | ;;;
|
|---|
| 297 | ;;; Return Nil if Word is a candidate for correction, otherwise
|
|---|
| 298 | ;;; return T and message as to why it isn't.
|
|---|
| 299 | ;;;
|
|---|
| 300 | (defun check-out-word-spelling (word folded)
|
|---|
| 301 | (declare (simple-string word))
|
|---|
| 302 | (let ((len (length word)))
|
|---|
| 303 | (cond ((= len 1)
|
|---|
| 304 | (message "Single character words are not in the dictionary.") t)
|
|---|
| 305 | ((> len spell:max-entry-length)
|
|---|
| 306 | (message "~A is too long for the dictionary." word) t)
|
|---|
| 307 | (t
|
|---|
| 308 | (multiple-value-bind (idx flagp) (spell:spell-try-word folded len)
|
|---|
| 309 | (when idx
|
|---|
| 310 | (message "Found it~:[~; because of ~A~]." flagp
|
|---|
| 311 | (spell:spell-root-word idx))
|
|---|
| 312 | t))))))
|
|---|
| 313 |
|
|---|
| 314 | ;;; Get-Word-Correction -- Internal
|
|---|
| 315 | ;;;
|
|---|
| 316 | ;;; Find all known close words to the either unknown or incorrectly
|
|---|
| 317 | ;;; spelled word we are checking. Word is the unmunged word, and Folded is
|
|---|
| 318 | ;;; the uppercased word. Mark is a mark which points to the beginning of
|
|---|
| 319 | ;;; the offending word. Return True if we successfully corrected the word.
|
|---|
| 320 | ;;;
|
|---|
| 321 | (defun get-word-correction (mark word folded)
|
|---|
| 322 | (let ((close-words (spell:spell-collect-close-words folded)))
|
|---|
| 323 | (declare (list close-words))
|
|---|
| 324 | (if close-words
|
|---|
| 325 | (with-pop-up-display (s :height 3)
|
|---|
| 326 | (do ((i 0 (1+ i))
|
|---|
| 327 | (words close-words (cdr words)))
|
|---|
| 328 | ((null words))
|
|---|
| 329 | (format s "~36R=~A " i (car words)))
|
|---|
| 330 | (finish-output s)
|
|---|
| 331 | (let* ((key-event (prompt-for-key-event
|
|---|
| 332 | :prompt "Correction choice: "))
|
|---|
| 333 | (num (digit-char-p (ext:key-event-char key-event) 36)))
|
|---|
| 334 | (cond ((not num) (return-from get-word-correction nil))
|
|---|
| 335 | ((> num (length close-words))
|
|---|
| 336 | (editor-error "Choice out of range."))
|
|---|
| 337 | (t
|
|---|
| 338 | (let ((s (nth num close-words)))
|
|---|
| 339 | (setf (gethash folded *spelling-corrections*) s)
|
|---|
| 340 | (undoable-replace-word mark word s)))))
|
|---|
| 341 | (return-from get-word-correction t))
|
|---|
| 342 | (with-pop-up-display (s :height 1)
|
|---|
| 343 | (write-line "No corrections found." s)
|
|---|
| 344 | nil))))
|
|---|
| 345 |
|
|---|
| 346 |
|
|---|
| 347 | ;;; Undoable-Replace-Word -- Internal
|
|---|
| 348 | ;;;
|
|---|
| 349 | ;;; Like Spell-Replace-Word, but makes annotations in buffer local variables
|
|---|
| 350 | ;;; so that "Undo Last Spelling Correction" can undo it.
|
|---|
| 351 | ;;;
|
|---|
| 352 | (defun undoable-replace-word (mark old new)
|
|---|
| 353 | (unless (hemlock-bound-p 'last-spelling-correction-mark)
|
|---|
| 354 | (let ((buffer (current-buffer)))
|
|---|
| 355 | (defhvar "Last Spelling Correction Mark"
|
|---|
| 356 | "This variable holds a park pointing to the last spelling correction."
|
|---|
| 357 | :buffer buffer :value (copy-mark (buffer-start-mark buffer)))
|
|---|
| 358 | (defhvar "Last Spelling Correction Words"
|
|---|
| 359 | "The replacement done for the last correction: (new . old)."
|
|---|
| 360 | :buffer buffer :value nil)))
|
|---|
| 361 | (move-mark (value last-spelling-correction-mark) mark)
|
|---|
| 362 | (setf (value last-spelling-correction-words) (cons new old))
|
|---|
| 363 | (spell-replace-word mark old new))
|
|---|
| 364 |
|
|---|
| 365 | |
|---|
| 366 |
|
|---|
| 367 | ;;;; Buffer Correction
|
|---|
| 368 |
|
|---|
| 369 | (defvar *spell-word-characters*
|
|---|
| 370 | (make-array char-code-limit :element-type 'bit :initial-element 0)
|
|---|
| 371 | "Characters that are legal in a word for spelling checking purposes.")
|
|---|
| 372 |
|
|---|
| 373 | (do-alpha-chars (c :both)
|
|---|
| 374 | (setf (sbit *spell-word-characters* (char-code c)) 1))
|
|---|
| 375 | (setf (sbit *spell-word-characters* (char-code #\')) 1)
|
|---|
| 376 |
|
|---|
| 377 |
|
|---|
| 378 | (defcommand "Correct Buffer Spelling" (p)
|
|---|
| 379 | "Correct spelling over whole buffer. A log of the found misspellings is
|
|---|
| 380 | kept in the buffer \"Spell Corrections\". For each unknown word the
|
|---|
| 381 | user may accept it, insert it in the dictionary, correct its spelling
|
|---|
| 382 | with one of the offered possibilities, replace the word with a user
|
|---|
| 383 | supplied word, or go into a recursive edit. Words may be added to the
|
|---|
| 384 | dictionary in advance from a text file (see the command \"Augment
|
|---|
| 385 | Spelling Dictionary\")."
|
|---|
| 386 | "Correct spelling over whole buffer."
|
|---|
| 387 | (declare (ignore p))
|
|---|
| 388 | (clrhash *ignored-misspellings*)
|
|---|
| 389 | (let* ((buffer (current-buffer))
|
|---|
| 390 | (log (or (make-buffer "Spelling Corrections")
|
|---|
| 391 | (getstring "Spelling Corrections" *buffer-names*)))
|
|---|
| 392 | (point (buffer-end (buffer-point log)))
|
|---|
| 393 | (*standard-output* (make-hemlock-output-stream point))
|
|---|
| 394 | (window (or (car (buffer-windows log)) (make-window point))))
|
|---|
| 395 | (format t "~&Starting spelling checking of buffer ~S.~2%"
|
|---|
| 396 | (buffer-name buffer))
|
|---|
| 397 | (spell:maybe-read-spell-dictionary)
|
|---|
| 398 | (correct-buffer-spelling buffer window)
|
|---|
| 399 | (delete-window window)
|
|---|
| 400 | (close *standard-output*)))
|
|---|
| 401 |
|
|---|
| 402 | ;;; CORRECT-BUFFER-SPELLING scans through buffer a line at a time, grabbing the
|
|---|
| 403 | ;;; each line's string and breaking it up into words using the
|
|---|
| 404 | ;;; *spell-word-characters* mask. We try the spelling of each word, and if it
|
|---|
| 405 | ;;; is unknown, we call FIX-WORD and resynchronize when it returns.
|
|---|
| 406 | ;;;
|
|---|
| 407 | (defun correct-buffer-spelling (buffer window)
|
|---|
| 408 | (do ((line (mark-line (buffer-start-mark buffer)) (line-next line))
|
|---|
| 409 | (info (if (hemlock-bound-p 'spell-information :buffer buffer)
|
|---|
| 410 | (variable-value 'spell-information :buffer buffer)
|
|---|
| 411 | (value spell-information)))
|
|---|
| 412 | (mask *spell-word-characters*)
|
|---|
| 413 | (word (make-string spell:max-entry-length)))
|
|---|
| 414 | ((null line))
|
|---|
| 415 | (declare (simple-bit-vector mask) (simple-string word))
|
|---|
| 416 | (block line
|
|---|
| 417 | (let* ((string (line-string line))
|
|---|
| 418 | (length (length string)))
|
|---|
| 419 | (declare (simple-string string))
|
|---|
| 420 | (do ((start 0 (or skip-apostrophes end))
|
|---|
| 421 | (skip-apostrophes nil nil)
|
|---|
| 422 | end)
|
|---|
| 423 | (nil)
|
|---|
| 424 | ;;
|
|---|
| 425 | ;; Find word start.
|
|---|
| 426 | (loop
|
|---|
| 427 | (when (= start length) (return-from line))
|
|---|
| 428 | (when (/= (bit mask (char-code (schar string start))) 0) (return))
|
|---|
| 429 | (incf start))
|
|---|
| 430 | ;;
|
|---|
| 431 | ;; Find the end.
|
|---|
| 432 | (setq end (1+ start))
|
|---|
| 433 | (loop
|
|---|
| 434 | (when (= end length) (return))
|
|---|
| 435 | (when (zerop (bit mask (char-code (schar string end)))) (return))
|
|---|
| 436 | (incf end))
|
|---|
| 437 | (multiple-value-setq (end skip-apostrophes)
|
|---|
| 438 | (correct-buffer-word-end string start end))
|
|---|
| 439 | ;;
|
|---|
| 440 | ;; Check word.
|
|---|
| 441 | (let ((word-len (- end start)))
|
|---|
| 442 | (cond
|
|---|
| 443 | ((= word-len 1))
|
|---|
| 444 | ((> word-len spell:max-entry-length)
|
|---|
| 445 | (format t "Not checking ~S -- too long for dictionary.~2%"
|
|---|
| 446 | word))
|
|---|
| 447 | (t
|
|---|
| 448 | ;;
|
|---|
| 449 | ;; Copy the word and uppercase it.
|
|---|
| 450 | (do* ((i (1- end) (1- i))
|
|---|
| 451 | (j (1- word-len) (1- j)))
|
|---|
| 452 | ((zerop j)
|
|---|
| 453 | (setf (schar word 0) (char-upcase (schar string i))))
|
|---|
| 454 | (setf (schar word j) (char-upcase (schar string i))))
|
|---|
| 455 | (unless (spell:spell-try-word word word-len)
|
|---|
| 456 | (move-to-position (current-point) start line)
|
|---|
| 457 | (fix-word (subseq word 0 word-len) (subseq string start end)
|
|---|
| 458 | window info)
|
|---|
| 459 | (let ((point (current-point)))
|
|---|
| 460 | (setq end (mark-charpos point)
|
|---|
| 461 | line (mark-line point)
|
|---|
| 462 | string (line-string line)
|
|---|
| 463 | length (length string))))))))))))
|
|---|
| 464 |
|
|---|
| 465 | ;;; CORRECT-BUFFER-WORD-END takes a line string from CORRECT-BUFFER-SPELLING, a
|
|---|
| 466 | ;;; start, and a end. It places end to exclude from the word apostrophes used
|
|---|
| 467 | ;;; for quotation marks, possessives, and funny plurals (e.g., A's and AND's).
|
|---|
| 468 | ;;; Every word potentially can be followed by "'s", and any clown can use the
|
|---|
| 469 | ;;; `` '' Scribe ligature. This returns the value to use for end of the word
|
|---|
| 470 | ;;; and the value to use as the end when continuing to find the next word in
|
|---|
| 471 | ;;; string.
|
|---|
| 472 | ;;;
|
|---|
| 473 | (defun correct-buffer-word-end (string start end)
|
|---|
| 474 | (cond ((and (> (- end start) 2)
|
|---|
| 475 | (char= (char-upcase (schar string (1- end))) #\S)
|
|---|
| 476 | (char= (schar string (- end 2)) #\'))
|
|---|
| 477 | ;; Use roots of possessives and funny plurals (e.g., A's and AND's).
|
|---|
| 478 | (values (- end 2) end))
|
|---|
| 479 | (t
|
|---|
| 480 | ;; Maybe backup over apostrophes used for quotation marks.
|
|---|
| 481 | (do ((i (1- end) (1- i)))
|
|---|
| 482 | ((= i start) (values end end))
|
|---|
| 483 | (when (char/= (schar string i) #\')
|
|---|
| 484 | (return (values (1+ i) end)))))))
|
|---|
| 485 |
|
|---|
| 486 | ;;; Fix-Word -- Internal
|
|---|
| 487 | ;;;
|
|---|
| 488 | ;;; Handles the case where the word has a known correction. If is does
|
|---|
| 489 | ;;; not then call Correct-Buffer-Word-Not-Found. In either case, the
|
|---|
| 490 | ;;; point is left at the place to resume checking.
|
|---|
| 491 | ;;;
|
|---|
| 492 | (defun fix-word (word unfolded-word window info)
|
|---|
| 493 | (declare (simple-string word unfolded-word))
|
|---|
| 494 | (let ((correction (gethash word *spelling-corrections*))
|
|---|
| 495 | (mark (current-point)))
|
|---|
| 496 | (cond (correction
|
|---|
| 497 | (format t "Replacing ~S with ~S.~%" unfolded-word correction)
|
|---|
| 498 | (spell-replace-word mark unfolded-word correction))
|
|---|
| 499 | ((and (value spell-ignore-uppercase)
|
|---|
| 500 | (every #'upper-case-p unfolded-word))
|
|---|
| 501 | (character-offset mark (length word))
|
|---|
| 502 | (unless (gethash word *ignored-misspellings*)
|
|---|
| 503 | (setf (gethash word *ignored-misspellings*) t)
|
|---|
| 504 | (format t "Ignoring ~S.~%" unfolded-word)))
|
|---|
| 505 | (t
|
|---|
| 506 | (correct-buffer-word-not-found word unfolded-word window info)))))
|
|---|
| 507 |
|
|---|
| 508 | (defun correct-buffer-word-not-found (word unfolded-word window info)
|
|---|
| 509 | (declare (simple-string word unfolded-word))
|
|---|
| 510 | (let* ((close-words (spell:spell-collect-close-words word))
|
|---|
| 511 | (close-words-len (length (the list close-words)))
|
|---|
| 512 | (mark (current-point))
|
|---|
| 513 | (wordlen (length word)))
|
|---|
| 514 | (format t "Unknown word: ~A~%" word)
|
|---|
| 515 | (cond (close-words
|
|---|
| 516 | (format t "~[~;A~:;Some~]~:* possible correction~[~; is~:;s are~]: "
|
|---|
| 517 | close-words-len)
|
|---|
| 518 | (if (= close-words-len 1)
|
|---|
| 519 | (write-line (car close-words))
|
|---|
| 520 | (let ((n 0))
|
|---|
| 521 | (dolist (w close-words (terpri))
|
|---|
| 522 | (format t "~36R=~A " n w)
|
|---|
| 523 | (incf n)))))
|
|---|
| 524 | (t
|
|---|
| 525 | (write-line "No correction possibilities found.")))
|
|---|
| 526 | (let ((point (buffer-point (window-buffer window))))
|
|---|
| 527 | (unless (displayed-p point window)
|
|---|
| 528 | (center-window window point)))
|
|---|
| 529 | (command-case
|
|---|
| 530 | (:prompt "Action: "
|
|---|
| 531 | :help "Type a single letter command, or help character for help."
|
|---|
| 532 | :change-window nil)
|
|---|
| 533 | (#\i "Insert unknown word into dictionary for future lookup."
|
|---|
| 534 | (spell:spell-add-entry word)
|
|---|
| 535 | (push word (spell-info-insertions info))
|
|---|
| 536 | (format t "~S added to dictionary.~2%" word))
|
|---|
| 537 | (#\c "Correct the unknown word with possible correct spellings."
|
|---|
| 538 | (unless close-words
|
|---|
| 539 | (write-line "There are no possible corrections.")
|
|---|
| 540 | (reprompt))
|
|---|
| 541 | (let ((num (if (= close-words-len 1) 0
|
|---|
| 542 | (digit-char-p (ext:key-event-char
|
|---|
| 543 | (prompt-for-key-event
|
|---|
| 544 | :prompt "Correction choice: "))
|
|---|
| 545 | 36))))
|
|---|
| 546 | (unless num (reprompt))
|
|---|
| 547 | (when (> num close-words-len)
|
|---|
| 548 | (beep)
|
|---|
| 549 | (write-line "Response out of range.")
|
|---|
| 550 | (reprompt))
|
|---|
| 551 | (let ((choice (nth num close-words)))
|
|---|
| 552 | (setf (gethash word *spelling-corrections*) choice)
|
|---|
| 553 | (spell-replace-word mark unfolded-word choice)))
|
|---|
| 554 | (terpri))
|
|---|
| 555 | (#\a "Accept the word as correct (that is, ignore it)."
|
|---|
| 556 | (character-offset mark wordlen))
|
|---|
| 557 | (#\r "Replace the unknown word with a supplied replacement."
|
|---|
| 558 | (let ((s (prompt-for-string
|
|---|
| 559 | :prompt "Replacement Word: "
|
|---|
| 560 | :default unfolded-word
|
|---|
| 561 | :help "String to replace the unknown word with.")))
|
|---|
| 562 | (setf (gethash word *spelling-corrections*) s)
|
|---|
| 563 | (spell-replace-word mark unfolded-word s))
|
|---|
| 564 | (terpri))
|
|---|
| 565 | (:recursive-edit
|
|---|
| 566 | "Go into a recursive edit and resume correction where the point is left."
|
|---|
| 567 | (do-recursive-edit)))))
|
|---|
| 568 |
|
|---|
| 569 | ;;; Spell-Replace-Word -- Internal
|
|---|
| 570 | ;;;
|
|---|
| 571 | ;;; Replaces Old with New, starting at Mark. The case of Old is used
|
|---|
| 572 | ;;; to derive the new case.
|
|---|
| 573 | ;;;
|
|---|
| 574 | (defun spell-replace-word (mark old new)
|
|---|
| 575 | (declare (simple-string old new))
|
|---|
| 576 | (let ((res (cond ((lower-case-p (schar old 0))
|
|---|
| 577 | (string-downcase new))
|
|---|
| 578 | ((lower-case-p (schar old 1))
|
|---|
| 579 | (let ((res (string-downcase new)))
|
|---|
| 580 | (setf (char res 0) (char-upcase (char res 0)))
|
|---|
| 581 | res))
|
|---|
| 582 | (t
|
|---|
| 583 | (string-upcase new)))))
|
|---|
| 584 | (with-mark ((m mark :left-inserting))
|
|---|
| 585 | (delete-characters m (length old))
|
|---|
| 586 | (insert-string m res))))
|
|---|
| 587 |
|
|---|
| 588 | |
|---|
| 589 |
|
|---|
| 590 | ;;;; User Spelling Dictionaries.
|
|---|
| 591 |
|
|---|
| 592 | (defvar *pathname-to-spell-info* (make-hash-table :test #'equal)
|
|---|
| 593 | "This maps dictionary files to spelling information.")
|
|---|
| 594 |
|
|---|
| 595 | (defhvar "Spell Information"
|
|---|
| 596 | "This is the information about a spelling dictionary and its incremental
|
|---|
| 597 | insertions."
|
|---|
| 598 | :value (make-spell-info nil))
|
|---|
| 599 |
|
|---|
| 600 | (define-file-option "Dictionary" (buffer file)
|
|---|
| 601 | (let* ((dict (merge-pathnames
|
|---|
| 602 | file
|
|---|
| 603 | (make-pathname :defaults (buffer-default-pathname buffer)
|
|---|
| 604 | :type "dict")))
|
|---|
| 605 | (dictp (probe-file dict)))
|
|---|
| 606 | (if dictp
|
|---|
| 607 | (set-buffer-spelling-dictionary-command nil dictp buffer)
|
|---|
| 608 | (loud-message "Couldn't find dictionary ~A." (namestring dict)))))
|
|---|
| 609 |
|
|---|
| 610 | ;;; SAVE-DICTIONARY-ON-WRITE is on the "Write File Hook" in buffers with
|
|---|
| 611 | ;;; the "dictionary" file option.
|
|---|
| 612 | ;;;
|
|---|
| 613 | (defun save-dictionary-on-write (buffer)
|
|---|
| 614 | (when (hemlock-bound-p 'spell-information :buffer buffer)
|
|---|
| 615 | (save-spelling-insertions
|
|---|
| 616 | (variable-value 'spell-information :buffer buffer))))
|
|---|
| 617 |
|
|---|
| 618 |
|
|---|
| 619 | (defcommand "Save Incremental Spelling Insertions" (p)
|
|---|
| 620 | "Append incremental spelling dictionary insertions to a file. The file
|
|---|
| 621 | is prompted for unless \"Set Buffer Spelling Dictionary\" has been
|
|---|
| 622 | executed in the buffer."
|
|---|
| 623 | "Append incremental spelling dictionary insertions to a file."
|
|---|
| 624 | (declare (ignore p))
|
|---|
| 625 | (let* ((info (value spell-information))
|
|---|
| 626 | (file (or (spell-info-pathname info)
|
|---|
| 627 | (value default-user-spelling-dictionary)
|
|---|
| 628 | (prompt-for-file
|
|---|
| 629 | :prompt "Dictionary File: "
|
|---|
| 630 | :default (dictionary-name-default)
|
|---|
| 631 | :must-exist nil
|
|---|
| 632 | :help
|
|---|
| 633 | "Name of the dictionary file to append dictionary insertions to."))))
|
|---|
| 634 | (save-spelling-insertions info file)
|
|---|
| 635 | (let* ((ginfo (variable-value 'spell-information :global))
|
|---|
| 636 | (insertions (spell-info-insertions ginfo)))
|
|---|
| 637 | (when (and insertions
|
|---|
| 638 | (prompt-for-y-or-n
|
|---|
| 639 | :prompt
|
|---|
| 640 | `("Global spelling insertions exist.~%~
|
|---|
| 641 | Save these to ~A also? "
|
|---|
| 642 | ,(namestring file)
|
|---|
| 643 | :default t
|
|---|
| 644 | :default-string "Y"))
|
|---|
| 645 | (save-spelling-insertions ginfo file))))))
|
|---|
| 646 |
|
|---|
| 647 | (defun save-spelling-insertions (info &optional
|
|---|
| 648 | (name (spell-info-pathname info)))
|
|---|
| 649 | (when (spell-info-insertions info)
|
|---|
| 650 | (with-open-file (stream name
|
|---|
| 651 | :direction :output :element-type 'base-char
|
|---|
| 652 | :if-exists :append :if-does-not-exist :create)
|
|---|
| 653 | (dolist (w (spell-info-insertions info))
|
|---|
| 654 | (write-line w stream)))
|
|---|
| 655 | (setf (spell-info-insertions info) ())
|
|---|
| 656 | (message "Incremental spelling insertions for ~A written."
|
|---|
| 657 | (namestring name))))
|
|---|
| 658 |
|
|---|
| 659 | (defcommand "Set Buffer Spelling Dictionary" (p &optional file buffer)
|
|---|
| 660 | "Prompts for the dictionary file to associate with the current buffer.
|
|---|
| 661 | If this file has not been read for any other buffer, then it is read.
|
|---|
| 662 | Incremental spelling insertions from this buffer can be appended to
|
|---|
| 663 | this file with \"Save Incremental Spelling Insertions\"."
|
|---|
| 664 | "Sets the buffer's spelling dictionary and reads it if necessary."
|
|---|
| 665 | (declare (ignore p))
|
|---|
| 666 | (maybe-read-default-user-spelling-dictionary)
|
|---|
| 667 | (let* ((file (truename (or file
|
|---|
| 668 | (prompt-for-file
|
|---|
| 669 | :prompt "Dictionary File: "
|
|---|
| 670 | :default (dictionary-name-default)
|
|---|
| 671 | :help
|
|---|
| 672 | "Name of the dictionary file to add into the current dictionary."))))
|
|---|
| 673 | (file-name (namestring file))
|
|---|
| 674 | (spell-info-p (gethash file-name *pathname-to-spell-info*))
|
|---|
| 675 | (spell-info (or spell-info-p (make-spell-info file)))
|
|---|
| 676 | (buffer (or buffer (current-buffer))))
|
|---|
| 677 | (defhvar "Spell Information"
|
|---|
| 678 | "This is the information about a spelling dictionary and its incremental
|
|---|
| 679 | insertions."
|
|---|
| 680 | :value spell-info :buffer buffer)
|
|---|
| 681 | (add-hook write-file-hook 'save-dictionary-on-write)
|
|---|
| 682 | (unless spell-info-p
|
|---|
| 683 | (setf (gethash file-name *pathname-to-spell-info*) spell-info)
|
|---|
| 684 | (read-spelling-dictionary-command nil file))))
|
|---|
| 685 |
|
|---|
| 686 | (defcommand "Read Spelling Dictionary" (p &optional file)
|
|---|
| 687 | "Adds entries to the dictionary from a file in the following format:
|
|---|
| 688 |
|
|---|
| 689 | entry1/flag1/flag2/flag3
|
|---|
| 690 | entry2
|
|---|
| 691 | entry3/flag1/flag2/flag3/flag4/flag5.
|
|---|
| 692 |
|
|---|
| 693 | The flags are single letter indicators of legal suffixes for the entry;
|
|---|
| 694 | the available flags and their correct use may be found at the beginning
|
|---|
| 695 | of spell-correct.lisp in the Hemlock sources. There must be exactly one
|
|---|
| 696 | entry per line, and each line must be flushleft."
|
|---|
| 697 | "Add entries to the dictionary from a text file in a specified format."
|
|---|
| 698 | (declare (ignore p))
|
|---|
| 699 | (spell:maybe-read-spell-dictionary)
|
|---|
| 700 | (spell:spell-read-dictionary
|
|---|
| 701 | (or file
|
|---|
| 702 | (prompt-for-file
|
|---|
| 703 | :prompt "Dictionary File: "
|
|---|
| 704 | :default (dictionary-name-default)
|
|---|
| 705 | :help
|
|---|
| 706 | "Name of the dictionary file to add into the current dictionary."))))
|
|---|
| 707 |
|
|---|
| 708 | (defun dictionary-name-default ()
|
|---|
| 709 | (make-pathname :defaults (buffer-default-pathname (current-buffer))
|
|---|
| 710 | :type "dict"))
|
|---|
| 711 |
|
|---|
| 712 | (defcommand "Add Word to Spelling Dictionary" (p)
|
|---|
| 713 | "Add the previous word to the spelling dictionary."
|
|---|
| 714 | "Add the previous word to the spelling dictionary."
|
|---|
| 715 | (declare (ignore p))
|
|---|
| 716 | (spell:maybe-read-spell-dictionary)
|
|---|
| 717 | (let ((word (region-to-string (spell-previous-word (current-point) nil))))
|
|---|
| 718 | ;;
|
|---|
| 719 | ;; SPELL:SPELL-ADD-ENTRY destructively uppercases word.
|
|---|
| 720 | (when (spell:spell-add-entry word)
|
|---|
| 721 | (message "Word ~(~S~) added to the spelling dictionary." word)
|
|---|
| 722 | (push word (spell-info-insertions (value spell-information))))))
|
|---|
| 723 |
|
|---|
| 724 | (defcommand "Remove Word from Spelling Dictionary" (p)
|
|---|
| 725 | "Prompts for word to remove from the spelling dictionary."
|
|---|
| 726 | "Prompts for word to remove from the spelling dictionary."
|
|---|
| 727 | (declare (ignore p))
|
|---|
| 728 | (spell:maybe-read-spell-dictionary)
|
|---|
| 729 | (let* ((word (prompt-for-string
|
|---|
| 730 | :prompt "Word to remove from spelling dictionary: "
|
|---|
| 731 | :trim t))
|
|---|
| 732 | (upword (string-upcase word)))
|
|---|
| 733 | (declare (simple-string word))
|
|---|
| 734 | (multiple-value-bind (index flagp)
|
|---|
| 735 | (spell:spell-try-word upword (length word))
|
|---|
| 736 | (unless index
|
|---|
| 737 | (editor-error "~A not in dictionary." upword))
|
|---|
| 738 | (if flagp
|
|---|
| 739 | (remove-spelling-word upword)
|
|---|
| 740 | (let ((flags (spell:spell-root-flags index)))
|
|---|
| 741 | (when (or (not flags)
|
|---|
| 742 | (prompt-for-y-or-n
|
|---|
| 743 | :prompt
|
|---|
| 744 | `("Deleting ~A also removes words formed from this root and these flags: ~% ~
|
|---|
| 745 | ~S.~%~
|
|---|
| 746 | Delete word anyway? "
|
|---|
| 747 | ,word ,flags)
|
|---|
| 748 | :default t
|
|---|
| 749 | :default-string "Y"))
|
|---|
| 750 | (remove-spelling-word upword)))))))
|
|---|
| 751 |
|
|---|
| 752 | ;;; REMOVE-SPELLING-WORD removes the uppercase word word from the spelling
|
|---|
| 753 | ;;; dictionary and from the spelling informations incremental insertions list.
|
|---|
| 754 | ;;;
|
|---|
| 755 | (defun remove-spelling-word (word)
|
|---|
| 756 | (let ((info (value spell-information)))
|
|---|
| 757 | (spell:spell-remove-entry word)
|
|---|
| 758 | (setf (spell-info-insertions info)
|
|---|
| 759 | (delete word (spell-info-insertions info) :test #'string=))))
|
|---|
| 760 |
|
|---|
| 761 | (defcommand "List Incremental Spelling Insertions" (p)
|
|---|
| 762 | "Display the incremental spelling insertions for the current buffer's
|
|---|
| 763 | associated spelling dictionary file."
|
|---|
| 764 | "Display the incremental spelling insertions for the current buffer's
|
|---|
| 765 | associated spelling dictionary file."
|
|---|
| 766 | (declare (ignore p))
|
|---|
| 767 | (let* ((info (value spell-information))
|
|---|
| 768 | (file (spell-info-pathname info))
|
|---|
| 769 | (insertions (spell-info-insertions info)))
|
|---|
| 770 | (declare (list insertions))
|
|---|
| 771 | (with-pop-up-display (s :height (1+ (length insertions)))
|
|---|
| 772 | (if file
|
|---|
| 773 | (format s "Incremental spelling insertions for dictionary ~A:~%"
|
|---|
| 774 | (namestring file))
|
|---|
| 775 | (write-line "Global incremental spelling insertions:" s))
|
|---|
| 776 | (dolist (w insertions)
|
|---|
| 777 | (write-line w s)))))
|
|---|
| 778 |
|
|---|
| 779 |
|
|---|
| 780 | |
|---|
| 781 |
|
|---|
| 782 | ;;;; Utilities for above stuff.
|
|---|
| 783 |
|
|---|
| 784 | ;;; SPELL-PREVIOUS-WORD returns as a region the current or previous word, using
|
|---|
| 785 | ;;; the spell word definition. If there is no such word, return nil. If end-p
|
|---|
| 786 | ;;; is non-nil, then mark ends the word even if there is a non-delimiter
|
|---|
| 787 | ;;; character after it.
|
|---|
| 788 | ;;;
|
|---|
| 789 | ;;; Actually, if mark is between the first character of a word and a
|
|---|
| 790 | ;;; non-spell-word characer, it is considered to be in that word even though
|
|---|
| 791 | ;;; that word is after the mark. This is because Hemlock's cursor is always
|
|---|
| 792 | ;;; displayed over the next character, so users tend to think of a cursor
|
|---|
| 793 | ;;; displayed on the first character of a word as being in that word instead of
|
|---|
| 794 | ;;; before it.
|
|---|
| 795 | ;;;
|
|---|
| 796 | (defun spell-previous-word (mark end-p)
|
|---|
| 797 | (with-mark ((point mark)
|
|---|
| 798 | (mark mark))
|
|---|
| 799 | (cond ((or end-p
|
|---|
| 800 | (zerop (character-attribute :spell-word-character
|
|---|
| 801 | (next-character point))))
|
|---|
| 802 | (unless (reverse-find-attribute mark :spell-word-character)
|
|---|
| 803 | (return-from spell-previous-word nil))
|
|---|
| 804 | (move-mark point mark)
|
|---|
| 805 | (reverse-find-attribute point :spell-word-character #'zerop))
|
|---|
| 806 | (t
|
|---|
| 807 | (find-attribute mark :spell-word-character #'zerop)
|
|---|
| 808 | (reverse-find-attribute point :spell-word-character #'zerop)))
|
|---|
| 809 | (cond ((and (> (- (mark-charpos mark) (mark-charpos point)) 2)
|
|---|
| 810 | (char= (char-upcase (previous-character mark)) #\S)
|
|---|
| 811 | (char= (prog1 (previous-character (mark-before mark))
|
|---|
| 812 | (mark-after mark))
|
|---|
| 813 | #\'))
|
|---|
| 814 | ;; Use roots of possessives and funny plurals (e.g., A's and AND's).
|
|---|
| 815 | (character-offset mark -2))
|
|---|
| 816 | (t
|
|---|
| 817 | ;; Maybe backup over apostrophes used for quotation marks.
|
|---|
| 818 | (loop
|
|---|
| 819 | (when (mark= point mark) (return-from spell-previous-word nil))
|
|---|
| 820 | (when (char/= (previous-character mark) #\') (return))
|
|---|
| 821 | (mark-before mark))))
|
|---|
| 822 | (region point mark)))
|
|---|