Skip to content

Instantly share code, notes, and snippets.

@lawlist
Created November 4, 2016 03:30
Show Gist options
  • Save lawlist/dfc75240cc06c66f19f48eaa3e570d7c to your computer and use it in GitHub Desktop.
Save lawlist/dfc75240cc06c66f19f48eaa3e570d7c to your computer and use it in GitHub Desktop.
Native splellchecking on OSX -- extracted from Aquamacs -- patch applies to Emacs 25 branch as of November 1, 2016 commit 5043e0a7e06dbf6507a59736e55bdde69d9a2a4e
diff --git a/lisp/term/common-win.el b/lisp/term/common-win.el
index 937e47f..165263a 100644
--- a/lisp/term/common-win.el
+++ b/lisp/term/common-win.el
@@ -73,6 +73,8 @@
(cons (logior (lsh 0 16) 12) 'ns-new-frame)
(cons (logior (lsh 0 16) 13) 'ns-toggle-toolbar)
(cons (logior (lsh 0 16) 14) 'ns-show-prefs)
+ (cons (logior (lsh 0 16) 20) 'ns-check-spelling)
+ (cons (logior (lsh 0 16) 21) 'ns-spelling-change)
))))
(set-terminal-parameter frame 'x-setup-function-keys t)))
diff --git a/lisp/term/ns-win.el b/lisp/term/ns-win.el
index e737131..d9795da 100644
--- a/lisp/term/ns-win.el
+++ b/lisp/term/ns-win.el
@@ -164,6 +164,8 @@ The properties returned may include `top', `left', `height', and `width'."
(define-key global-map [ns-open-file] 'ns-find-file)
(define-key global-map [ns-open-temp-file] [ns-open-file])
(define-key global-map [ns-change-font] 'ns-respond-to-change-font)
+(define-key global-map [ns-check-spelling] 'ns-respond-to-find-next-misspelling)
+(define-key global-map [ns-spelling-change] 'ns-respond-to-change-spelling)
(define-key global-map [ns-open-file-line] 'ns-open-file-select-line)
(define-key global-map [ns-spi-service-call] 'ns-spi-service-call)
(define-key global-map [ns-new-frame] 'make-frame)
@@ -660,6 +662,31 @@ See the documentation of `create-fontset-from-fontset-spec' for the format.")
(defvar ns-reg-to-script) ; nsfont.m
+(autoload 'ns-toggle-spellchecker-panel "flyspell"
+ "Show NSSpellChecker spellingPanel, and call
+ns-highlight-misspelling-and-suggest. If panel
+is already visible, close it." t)
+
+(autoload 'ns-highlight-misspelling-and-suggest "flyspell"
+ "Search forward in current buffer for first misspelling, looping if end
+is reached. If found, set region to the misspelling, apply face
+flyspell-incorrect, and show word in OS X spelling panel" nil)
+
+(defun ns-respond-to-change-spelling (start end)
+ "Respond to changeSpelling: event, expecting ns-spelling-text
+to substitute for selected buffer text."
+ (interactive "r")
+ (if mark-active
+ (delete-region start end))
+ (insert ns-spelling-text))
+
+(defun ns-respond-to-find-next-misspelling ()
+ "Respond to checkSpelling: event. Also called by Spellchecker
+panel immediately after correcting a word in a buffer."
+ (interactive)
+ (ns-highlight-misspelling-and-suggest)
+ )
+
;; This maps font registries (not exposed by NS APIs for font selection) to
;; Unicode scripts (which can be mapped to Unicode character ranges which are).
;; See ../international/fontset.el
diff --git a/lisp/textmodes/flyspell.el b/lisp/textmodes/flyspell.el
index 730b55f..eaa5357 100644
--- a/lisp/textmodes/flyspell.el
+++ b/lisp/textmodes/flyspell.el
@@ -40,6 +40,7 @@
(require 'ispell)
(eval-when-compile (require 'cl-lib))
+(require 'thingatpt) ;; use (word-at-point) in ns-spellchecking functions
;;*---------------------------------------------------------------------*/
;;* Group ... */
@@ -282,11 +283,452 @@ If this variable is nil, all regions are treated as small."
'flyspell-auto-correct-word))))
(defcustom flyspell-auto-correct-binding
- [(control ?\;)]
+ [(control ?\')]
"The key binding for flyspell auto correction."
:type 'key-sequence
:group 'flyspell)
+(defcustom flyspell-mode-auto-on t
+ "Non-nil means that executing `flyspell-region' or `flyspell-buffer'
+will automatically turn on `flyspell-mode' for that buffer."
+ :group 'flyspell
+ :type 'boolean)
+
+;; **********************************************************************
+;; functions that use NSSpellChecker as the spellchecking
+;; engine, instead of ispell or aspell
+
+(defun ns-spellcheck-and-flyspell-word (beg end)
+ "Use NSSpellChecker to locate misspelled words within range
+BEG to END in current buffer. Run flyspell-word on the misspelling,
+and repeat search if word is not considered a misspelling by flyspell.
+Returns buffer location of misspelled word if found, or nil. As a side
+effect, marks the misspelled word (if found) with face flyspell-incorrect."
+ (let ((pos beg)
+ misspell-location
+ misspell-end
+ done)
+ ;; update dictionary if needed
+ (unless (string= ispell-current-dictionary
+ (ns-spellchecker-current-language))
+ (ispell-change-dictionary (ns-spellchecker-current-language)))
+ ;; loop through until we find a misspelling or end of string
+ (while (not done)
+ (setq misspell-location
+ (ns-spellchecker-check-spelling (buffer-substring pos end)
+ (current-buffer)))
+ (if misspell-location
+ (save-excursion
+ (setq misspell-end
+ (+ pos (car misspell-location) (cdr misspell-location)))
+ (goto-char misspell-end)
+ (if (flyspell-word) ;; returns t if not misspelled
+ ;; ignore misspelling if flyspell-word says it's OK,
+ ;; but continue checking
+ (setq misspell-location nil)
+ ;; if flyspell-word concurs, we've found a misspelling & are done
+ (setq done t)
+ ))
+ ;; no misspellings in string; finish.
+ (setq done t))
+ (unless done
+ ;; check remainder of string
+ (setq pos misspell-end)))
+ ;; if a misspelling has been found, report is location (otherwise nil)
+ (when misspell-location
+ (cons (+ pos (car misspell-location)) misspell-end))
+ ))
+
+(defun ns-find-next-misspelling (&optional noskip)
+ "Move forward in buffer to next misspelling; set region to the word,
+and apply flyspell-incorrect face. If NOSKIP is non-nil, don't skip
+the current highlighted word (if any)."
+;; search forward for a spelling error according to NSSpellChecker
+;; do flyspell-word or equivalent to see if it is really misspelled
+;; (e.g. not TeX or other filtered expression)
+;; if it is, then also highlight it, and put it in the spelling panel
+ (interactive)
+ (unless (string= ispell-current-dictionary
+ (ns-spellchecker-current-language))
+ (ispell-change-dictionary (ns-spellchecker-current-language)))
+ (let* ((pos (if mark-active
+ ;; use beginning of region as start point for spellchecking,
+ ;; if there is an active region
+ (min (mark) (point))
+ (point)))
+ (beg (point-min))
+ (end (point-max))
+ ;; (chunk-begin pos)
+ misspell-location
+ ;; chunk-end
+ ;; approx-end
+ )
+ (save-excursion ;retain point & region if no misspelling found
+ (goto-char pos)
+ ;; this bit is needed to unhighlight the previous word, if it
+ ;; is ignored or learned in the spelling panel
+ (let ((word (word-at-point)))
+ (if (and word
+ (not (ns-spellchecker-check-spelling word (current-buffer))))
+ (flyspell-unhighlight-at (point))))
+ ;; If midway through a word, start at search at next word;
+ ;; but don't skip an entire word
+ (if (backward-word)
+ (forward-word))
+ ;; When a selection is active, always skip the first word or
+ ;; partial word (as TextEdit does), so we don't spellcheck
+ ;; the same word again
+ (if (and mark-active (not noskip)) (forward-word))
+ (flyspell-word)
+ (setq pos (point))
+ ;; if region from point to end is larger than 1.5x
+ ;; NS-SPELLCHECKER-CHUNK-SIZE chars, then check text in smaller chunks
+ ;; (if (< (- end pos) (* 1.5 ns-spellchecker-chunk-size))
+ ;; (setq chunk-end end)
+ ;; (setq chunk-end (+ pos ns-spellchecker-chunk-size))
+ ;; ;; try not to spellcheck across a sentence boundary, to keep
+ ;; ;; grammar checking happy; make sure we at least don't split words.
+ ;; (save-excursion
+ ;; (goto-char approx-end)
+ ;; (forward-sentence)
+ ;; (setq chunk-end (point))
+ ;; ;; If sentence boundary not found within 10% after
+ ;; ;; specified chunk size, look for first sentence
+ ;; ;; boundary up to 10% before desired chunk size.
+ ;; ;; If not found, use first word boundary after desired chunk size
+ ;; (if (> (abs (- chunk-end approx-end))
+ ;; (* 0.1 ns-spellchecker-chunk-size))
+ ;; (progn
+ ;; ;; find nearest previous sentence boundary
+ ;; (backward-sentence)
+ ;; (setq chunk-end (point))
+ ;; ;; check whether it's within 10% of specified value
+ ;; (if (> (abs (- approx-end chunk-end))
+ ;; (* 0.1 ns-spellchecker-chunk-size))
+ ;; (progn
+ ;; ;; if not, use next word boundary
+ ;; (goto-char approx-end)
+ ;; (forward-word)
+ ;; (setq chunk-end (point))))))))
+ ;; (while (and
+ ;; (eq (car misspelled-location) -1)
+ ;; (not overlap))
+ ;; (setq misspell-location
+ ;; (ns-spellchecker-check-spelling
+ ;; (buffer-substring chunk-begin chunk-end)))
+ ;; ;; )
+ ;; (setq misspell-location
+ ;; (ns-spellchecker-check-spelling (buffer-substring pos end)))
+ ;; (if (= (car misspell-location) -1)
+ ;; nil
+ ;; (setq misspell-beg
+ ;; (+ pos (car misspell-location))
+ ;; misspell-end
+ ;; (+ pos (car misspell-location) (cdr misspell-location)))
+ ;; (goto-char misspell-end)
+ ;; (setq misspell-found-p (not (flyspell-word))))
+ ;; (unless misspell-found-p
+ ;; (setq misspell-location
+ ;; (ns-spellchecker-check-spelling (buffer-substring beg pos)))
+ ;; (if (= (car misspell-location) -1)
+ ;; nil
+ ;; (setq misspell-beg
+ ;; (+ beg (car misspell-location))
+ ;; misspell-end
+ ;; (+ beg (car misspell-location) (cdr misspell-location)))
+ ;; (goto-char misspell-end)
+ ;; (setq misspell-found-p (not (flyspell-word)))))
+ ;; (if misspell-found-p
+ ;; return start and end of the misspelled word, or nil if none found
+ ;; (cons misspell-beg misspell-end))
+ (setq misspell-location
+ (ns-spellcheck-and-flyspell-word pos end))
+ (if (not misspell-location)
+ (setq misspell-location (ns-spellcheck-and-flyspell-word beg pos)))
+ ;; returns nil if we haven't found a misspelling
+ misspell-location
+ )))
+
+(defun ns-highlight-misspelling-and-suggest (&optional noskip)
+ "Search forward in current buffer for first misspelling, looping if end
+is reached. If found, set region to the misspelling, apply face
+flyspell-incorrect, and show word in OS X spelling panel. If
+NOSKIP is non-nil, don't skip the current highlighted word (if any)."
+ (interactive)
+ (let* ((misspell-region (ns-find-next-misspelling noskip))
+ (misspell-beg (car misspell-region))
+ (misspell-end (cdr misspell-region))
+ word)
+ (if (not misspell-region)
+ ;; no misspelling found; blank and beep the spelling panel
+ (progn
+ (ns-spellchecker-show-word "")
+ (message "Spell checker: found no errors"))
+ ;; misspelling found; set region to mispelled word, and show
+ ;; in spelling panel
+ (goto-char misspell-end)
+ (push-mark misspell-beg 'no-msg 'activate)
+ (setq word (buffer-substring misspell-beg misspell-end))
+ (ns-spellchecker-show-word word)))
+ (if (and flyspell-mode-auto-on (not flyspell-mode))
+ (turn-on-flyspell)))
+
+(defun ns-start-spellchecker ()
+ "Show NSSpellChecker spellingPanel, and call
+ns-highlight-misspelling-and-suggest, which see."
+ (interactive)
+ (ns-popup-spellchecker-panel)
+ (ns-highlight-misspelling-and-suggest))
+
+(defun ns-toggle-spellchecker-panel ()
+ "Show NSSpellChecker spellingPanel, and call
+ns-highlight-misspelling-and-suggest. If panel
+is already visible, close it instead."
+ (interactive)
+ (if (ns-spellchecker-panel-visible-p)
+ (ns-close-spellchecker-panel)
+ (ns-popup-spellchecker-panel)
+ ;; panel shouldn't skip past currently selected word, if there is one
+ (ns-highlight-misspelling-and-suggest 'noskip)))
+
+;;;###autoload
+(defun spellcheck-now ()
+ "Start spellchecking, using OS X spellchecker
+(`ns-highlight-misspelling-and-suggest') or `ispell-buffer' (which see),
+depending on value of `ispell-program-name'."
+ (interactive)
+ (if (string= ispell-program-name "NSSpellChecker")
+ (ns-highlight-misspelling-and-suggest)
+ (ispell-buffer)))
+
+;;;###autoload
+(defun spellchecker-panel-or-ispell ()
+ "Calls `ns-toggle-spellchecker-panel' or `ispell' (which see), depending
+on current value of `ispell-program-name'."
+ (interactive)
+ (if (string= ispell-program-name "NSSpellChecker")
+ (ns-toggle-spellchecker-panel)
+ (ispell)))
+
+(defun ns-flyspell-region (beg end)
+ "Flyspell text between BEG and END using ns-spellchecker-check-spelling."
+ (interactive "r")
+ (unless (string= ispell-current-dictionary
+ (ns-spellchecker-current-language))
+ (ispell-change-dictionary (ns-spellchecker-current-language)))
+ (save-excursion
+ (let ((spellcheck-position beg)
+ (count 0)
+ ;; if called by ns-flyspell-large-region, then report progress
+ ;; relative to "large region" extents instead of simply extents of
+ ;; the current sub-region
+ (progress-beg
+ (if (boundp 'ns-flyspell-large-region-beg)
+ ns-flyspell-large-region-beg
+ beg))
+ (progress-end
+ (if (boundp 'ns-flyspell-large-region-end)
+ ns-flyspell-large-region-end
+ end))
+ spellcheck-text
+ ns-spellcheck-output
+ misspelled-location
+ misspelled-length)
+ ;; remove any existing flyspell overlays before checking
+ (flyspell-delete-region-overlays beg end)
+ (while (< spellcheck-position end)
+ ;; report progress
+ (if flyspell-issue-message-flag
+ (message "Spell Checking...%d%% [%s]"
+ (* 100 (/ (float (- spellcheck-position progress-beg))
+ (- progress-end progress-beg)))
+ (word-at-point)))
+ ;; extract text from last checked word to end
+ (setq spellcheck-text (buffer-substring spellcheck-position end))
+ ;; find (position . length) of first misspelled word in extracted text
+ (setq ns-spellcheck-output
+ ;; returns nil if no misspellings found
+ (ns-spellchecker-check-spelling spellcheck-text (current-buffer)))
+ (if ns-spellcheck-output
+ ;; found misspelled word
+ (progn
+ (setq misspelled-location
+ (+ (car ns-spellcheck-output) spellcheck-position))
+ (setq misspelled-length (cdr ns-spellcheck-output))
+ ;; start next check after current found word
+ (setq spellcheck-position
+ (+ misspelled-location misspelled-length))
+ ;; use flyspell-word to filter and mark misspellings
+ (goto-char spellcheck-position)
+ (flyspell-word))
+ ;; no misspellings found; we've reached the end of chunk
+ (setq spellcheck-position end))
+ )
+ (if (and (not (boundp 'ns-flyspell-large-region-end))
+ flyspell-issue-message-flag)
+ (message "Spell Checking completed."))
+ ;; function returns end of this chunk
+ end)))
+
+(defun ns-flyspell-large-region (beg end)
+ "Flyspell text between BEG and END using ns-spellchecker-check-spelling.
+Break long text into chunks of approximate size NS-SPELLCHECKER-CHUNK-SIZE,
+dividing at sentence boundaries where possible, or at word boundaries if
+sentence boundaries are too far between."
+ (interactive "r")
+ (save-excursion
+ (let ((inhibit-redisplay t)
+ (chunk-beg beg)
+ (ns-flyspell-large-region-beg beg)
+ (ns-flyspell-large-region-end end)
+ chunk-end
+ approx-end)
+ (while
+ (<
+ (progn
+ ;; if length from chunk start to overall end is less
+ ;; than 1.5x chunk size, set chunk end to overall end
+ (if (< (- end chunk-beg) (* 1.5 ns-spellchecker-chunk-size))
+ (setq chunk-end end)
+ ;; otherwise, set end to sentence boundary near desired chunk size
+ (save-excursion
+ (setq approx-end (+ chunk-beg ns-spellchecker-chunk-size))
+ (goto-char approx-end)
+ (forward-sentence)
+ (setq chunk-end (point))
+ ;; If sentence boundary not found within 10% after
+ ;; specified chunk size, look for first sentence
+ ;; boundary up to 10% before desired chunk size.
+ ;; If not found, use first word boundary after desired chunk size
+ (if (> (- chunk-end approx-end)
+ (* 0.1 ns-spellchecker-chunk-size))
+ (progn
+ ;; find nearest previous sentence boundary
+ (backward-sentence)
+ (setq chunk-end (point))
+ ;; check whether it's within 10% of specified value
+ (if (> (- approx-end chunk-end)
+ (* 0.1 ns-spellchecker-chunk-size))
+ (progn
+ ;; if not, use next word boundary
+ (goto-char approx-end)
+ (forward-word)
+ (setq chunk-end (point))))))))
+ ;; make sure we haven't extended the chunk beyond the region
+ (if (> chunk-end end)
+ (setq chunk-end end))
+ ;; check spelling of chunk, returning chunk end location
+ (ns-flyspell-region chunk-beg chunk-end))
+ end)
+ ;; if not done, start new chunk at previous chunk end
+ (setq chunk-beg chunk-end))
+ ;; when done, report completion
+ (if flyspell-issue-message-flag (message "Spell Checking completed."))
+ )))
+
+;; **********************************************************************
+;; global-flyspell-mode and automatic text-mode flyspelling
+
+(defun maybe-turn-on-flyspell ()
+ "Run `turn-on-flyspell' for current buffer, unless one of
+`global-flyspell-inhibit-functions' returns t"
+ (unless (run-hook-with-args-until-success
+ 'global-flyspell-inhibit-functions)
+ (turn-on-flyspell)))
+
+;; turn on flyspell-mode for all buffers, with exception of new fundamental-mode
+;; buffers and those returning t for any function listed in
+;; `global-flyspell-inhibit-functions'
+;;;###autoload
+(define-globalized-minor-mode global-flyspell-mode flyspell-mode
+ maybe-turn-on-flyspell)
+
+(defcustom global-flyspell-inhibit-functions
+ '(global-flyspell-default-inhibit-function)
+ "List of functions to be called before `global-flyspell-mode' activates
+flyspell in a buffer. Those functions are called one by one, with no
+arguments, until one of them returns a non-nil value, and thus, prevents
+activation of `flyspell-mode' (but only via `global-flyspell-mode')."
+ :group 'flyspell
+ :type 'hook)
+
+(defcustom global-flyspell-inhibit-buffer-names
+ nil
+ "List of buffer names in which `global-flyspell-mode' will not
+ activate `flyspell-mode'"
+ :group 'flyspell
+ :type '(repeat (string)))
+
+(defun global-flyspell-default-inhibit-function ()
+ "Return non-nil if current buffer: has a major mode with a
+property named mode-class with value special; has a name starting
+with a space and is not visiting a file; is designated by its
+name as a buffer to be displayed specially; or is listed by name
+in `global-flyspell-inhibit-buffer-names'. This is used to
+indicate that the buffer should not have `flyspell-mode' turned on
+by `global-flyspell-mode'."
+ (or
+ ;; buffers whose major-mode is marked as "special"
+ (eq (get major-mode 'mode-class) 'special)
+ ;; buffers whose name starts with space, if not visiting a file
+ (and (char-equal ?\ (aref (buffer-name) 0))
+ (not (buffer-file-name)))
+ ;; buffers designated for special display (e.g. dedicated frame)
+ (special-display-p (buffer-name))
+ ;; buffer with name listed in global-flyspell-inhibit-buffer-names
+ (member (buffer-name) global-flyspell-inhibit-buffer-names)
+ ))
+
+;;;###autoload
+(defun flyspell-text-modes ()
+ "Use Flyspell in Text mode and related modes.
+Applies to all buffers that use modes related to Text mode,
+both existing buffers and buffers that you subsequently create.
+Turns off `flyspell-all-modes' if on."
+ (interactive)
+ (if global-flyspell-mode
+ (global-flyspell-mode -1))
+ (add-hook 'text-mode-hook 'turn-on-flyspell)
+ (customize-mark-as-set 'text-mode-hook)
+ (dolist (buffer (buffer-list))
+ (with-current-buffer buffer
+ (let ((textual (or (derived-mode-p 'text-mode) text-mode-variant)))
+ (if textual
+ (turn-on-flyspell)
+ (if flyspell-mode (turn-off-flyspell))))))
+ (message "Flyspell activated in Text modes"))
+
+;;;###autoload
+(defun flyspell-all-modes ()
+ "Use Flyspell in all major modes.
+Applies both to existing buffers and buffers that you subsequently
+create. Turns off `flyspell-text-modes' if on."
+ (interactive)
+ (let ((disable-for-text (memq 'turn-on-flyspell
+ text-mode-hook)))
+ (if disable-for-text
+ (progn
+ (remove-hook 'text-mode-hook 'turn-on-flyspell)
+ (customize-mark-as-set 'text-mode-hook)))
+ (global-flyspell-mode 1)
+ (message "Flyspell activated in all modes")))
+
+;;;###autoload
+(defun flyspell-no-modes ()
+ "Turn off `flyspell-text-modes' or `flyspell-all-modes' if on.
+Also turns off flyspell-mode in all existing buffers."
+ (interactive)
+ (let ((disable-for-text (memq 'turn-on-flyspell
+ text-mode-hook)))
+ (if disable-for-text
+ (progn
+ (remove-hook 'text-mode-hook 'turn-on-flyspell)
+ (customize-mark-as-set 'text-mode-hook)))
+ ;; this turns off flyspell everywhere, whether or not the global mode was
+ ;; previously on:
+ (global-flyspell-mode -1)
+ (message "Flyspell deactivated in all modes")))
+
;;*---------------------------------------------------------------------*/
;;* Mode specific options */
;;* ------------------------------------------------------------- */
@@ -579,7 +1021,8 @@ in your init file.
(defun flyspell-hack-local-variables-hook ()
;; When local variables are loaded, see if the dictionary context
;; has changed.
- (flyspell-accept-buffer-local-defs 'force))
+ ;; (flyspell-accept-buffer-local-defs 'force)
+ )
(defun flyspell-kill-ispell-hook ()
(setq flyspell-last-buffer nil)
@@ -1151,6 +1594,8 @@ misspelling and skips redundant spell-checking step."
(setq flyspell-word-cache-end end)
(setq flyspell-word-cache-word word)
;; now check spelling of word.
+ (if (string= ispell-program-name "NSSpellChecker")
+ (setq poss (ns-spellchecker-parse-output word))
(if (not known-misspelling)
(progn
(ispell-send-string "%\n")
@@ -1177,7 +1622,7 @@ misspelling and skips redundant spell-checking step."
(setq poss (ispell-parse-output (car ispell-filter)))))
;; Else, this was a known misspelling to begin with, and
;; we should forge an ispell return value.
- (setq poss (list word 1 nil nil)))
+ (setq poss (list word 1 nil nil))))
(let ((res (cond ((eq poss t)
;; correct
(setq flyspell-word-cache-result t)
@@ -1534,7 +1979,12 @@ The buffer to mark them in is `flyspell-large-region-buffer'."
;;*---------------------------------------------------------------------*/
(defun flyspell-large-region (beg end)
(let* ((curbuf (current-buffer))
- (buffer (get-buffer-create "*flyspell-region*")))
+ (buffer (get-buffer-create "*flyspell-region*"))
+ (current-dict-name (or ispell-local-dictionary ispell-dictionary-internal))
+ (current-dict
+ (if (eq ispell-use-cocoaspell-internal 'full)
+ (aspell-dict-abbrev current-dict-name)
+ current-dict-name)))
(setq flyspell-external-ispell-buffer buffer)
(setq flyspell-large-region-buffer curbuf)
(setq flyspell-large-region-beg beg)
@@ -1547,8 +1997,7 @@ The buffer to mark them in is `flyspell-large-region-buffer'."
(set-buffer curbuf)
(ispell-set-spellchecker-params) ; Initialize variables and dicts alists
;; Local dictionary becomes the global dictionary in use.
- (setq ispell-current-dictionary
- (or ispell-local-dictionary ispell-dictionary))
+ (setq ispell-current-dictionary current-dict-name)
(setq ispell-current-personal-dictionary
(or ispell-local-pdict ispell-personal-dictionary))
(let ((args (ispell-get-ispell-args))
@@ -1557,7 +2006,7 @@ The buffer to mark them in is `flyspell-large-region-buffer'."
(if (and ispell-current-dictionary ; use specified dictionary
(not (member "-d" args))) ; only define if not overridden
(setq args
- (append (list "-d" ispell-current-dictionary) args)))
+ (append (list "-d" current-dict) args)))
(if ispell-current-personal-dictionary ; use specified pers dict
(setq args
(append args
@@ -1625,9 +2074,21 @@ The buffer to mark them in is `flyspell-large-region-buffer'."
(let ((old beg))
(setq beg end)
(setq end old)))
+ (if (string= ispell-program-name "NSSpellChecker")
+ (progn
+ (if (> (- end beg) (* ns-spellchecker-chunk-size 1.5))
+ ;; large; spellcheck in chunks.
+ (ns-flyspell-large-region beg end)
+ ;; not so large; spellcheck all at once.
+ (ns-flyspell-region beg end))
+ ;; check for and mark consecutive repeated words
+ (flyspell-check-region-doublons beg end))
(if (and flyspell-large-region (> (- end beg) flyspell-large-region))
(flyspell-large-region beg end)
- (flyspell-small-region beg end)))))
+ (flyspell-small-region beg end))))
+ (if (and flyspell-mode-auto-on (not flyspell-mode))
+ (turn-on-flyspell))
+ ))
;;*---------------------------------------------------------------------*/
;;* flyspell-buffer ... */
@@ -1751,10 +2212,10 @@ FACE and MOUSE-FACE specify the `face' and `mouse-face' properties
for the overlay."
(let ((overlay (make-overlay beg end nil t nil)))
(overlay-put overlay 'face face)
- (overlay-put overlay 'mouse-face mouse-face)
+ ;; (overlay-put overlay 'mouse-face mouse-face)
(overlay-put overlay 'flyspell-overlay t)
(overlay-put overlay 'evaporate t)
- (overlay-put overlay 'help-echo "mouse-2: correct word at point")
+ ;; (overlay-put overlay 'help-echo "mouse-2: correct word at point")
(overlay-put overlay 'keymap flyspell-mouse-map)
(when (eq face 'flyspell-incorrect)
(and (stringp flyspell-before-incorrect-word-string)
@@ -1955,8 +2416,10 @@ This command proposes various successive corrections for the current word."
(word (car word))
poss ispell-filter)
(setq flyspell-auto-correct-word word)
- ;; Now check spelling of word..
- (ispell-send-string "%\n") ;Put in verbose mode.
+ ;; now check spelling of word.
+ (if (string= ispell-program-name "NSSpellChecker")
+ (setq poss (ns-spellchecker-parse-output word))
+ (ispell-send-string "%\n") ;put in verbose mode
(ispell-send-string (concat "^" word "\n"))
;; Wait until ispell has processed word.
(while (progn
@@ -1969,7 +2432,7 @@ This command proposes various successive corrections for the current word."
(or ispell-filter
(setq ispell-filter '(*)))
(if (consp ispell-filter)
- (setq poss (ispell-parse-output (car ispell-filter))))
+ (setq poss (ispell-parse-output (car ispell-filter)))))
(cond
((or (eq poss t) (stringp poss))
;; Don't correct word.
@@ -2115,6 +2578,8 @@ If OPOINT is non-nil, restore point there after adjusting it for replacement."
(word (car word))
poss ispell-filter)
;; now check spelling of word.
+ (if (string= ispell-program-name "NSSpellChecker")
+ (setq poss (ns-spellchecker-parse-output word))
(ispell-send-string "%\n") ;put in verbose mode
(ispell-send-string (concat "^" word "\n"))
;; wait until ispell has processed word
@@ -2128,7 +2593,7 @@ If OPOINT is non-nil, restore point there after adjusting it for replacement."
(or ispell-filter
(setq ispell-filter '(*)))
(if (consp ispell-filter)
- (setq poss (ispell-parse-output (car ispell-filter))))
+ (setq poss (ispell-parse-output (car ispell-filter)))))
(cond
((or (eq poss t) (stringp poss))
;; don't correct word
@@ -2141,8 +2606,10 @@ If OPOINT is non-nil, restore point there after adjusting it for replacement."
poss word cursor-location start end opoint))
(t
;; The word is incorrect, we have to propose a replacement.
- (flyspell-do-correct (flyspell-emacs-popup event poss word)
- poss word cursor-location start end opoint)))
+ ;; restore point now, in case user selects a non-flyspell option.
+ (goto-char opoint)
+ (flyspell-emacs-popup
+ event poss word cursor-location start end opoint)))
(ispell-pdict-save t)))))
;;*---------------------------------------------------------------------*/
@@ -2157,14 +2624,19 @@ If OPOINT is non-nil, restore point there after adjusting it for replacement."
nil)
((eq replace 'save)
(goto-char save)
+ (if (string= ispell-program-name "NSSpellChecker")
+ (ns-spellchecker-learn-word word)
(ispell-send-string (concat "*" word "\n"))
;; This was added only to the XEmacs side in revision 1.18 of
;; flyspell. I assume its absence on the Emacs side was an
;; oversight. --Stef
(ispell-send-string "#\n")
+ (setq ispell-pdict-modified-p '(t)))
(flyspell-unhighlight-at cursor-location)
- (setq ispell-pdict-modified-p '(t)))
+ )
((or (eq replace 'buffer) (eq replace 'session))
+ (if (string= ispell-program-name "NSSpellChecker")
+ (ns-spellchecker-ignore-word word (current-buffer))
(ispell-send-string (concat "@" word "\n"))
(add-to-list 'ispell-buffer-session-localwords word)
(or ispell-buffer-local-name ; session localwords might conflict
@@ -2173,9 +2645,11 @@ If OPOINT is non-nil, restore point there after adjusting it for replacement."
(if (null ispell-pdict-modified-p)
(setq ispell-pdict-modified-p
(list ispell-pdict-modified-p)))
- (goto-char save)
+ (goto-char save))
(if (eq replace 'buffer)
- (ispell-add-per-file-word-list word)))
+ (ispell-add-per-file-word-list word))
+ (flyspell-unhighlight-at cursor-location)
+ )
(replace
;; This was added only to the Emacs side. I assume its absence on
;; the XEmacs side was an oversight. --Stef
@@ -2190,7 +2664,8 @@ If OPOINT is non-nil, restore point there after adjusting it for replacement."
(delete-region start end)
(goto-char start)
(funcall flyspell-insert-function new-word)
- (if flyspell-abbrev-p
+ (if (and (not (string= ispell-program-name "NSSpellChecker"))
+ flyspell-abbrev-p)
(flyspell-define-abbrev word new-word)))
;; In the original Emacs code, this was only called in the body
;; of the if. I arbitrarily kept the XEmacs behavior instead.
@@ -2216,7 +2691,9 @@ If OPOINT is non-nil, restore point there after adjusting it for replacement."
;;*---------------------------------------------------------------------*/
;;* flyspell-emacs-popup ... */
;;*---------------------------------------------------------------------*/
-(defun flyspell-emacs-popup (event poss word)
+;; flyspell-emacs-popup modified to define & use a keymap, so we can
+;; easily append another keymap (as "parent-keymap")
+(defun flyspell-emacs-popup (event poss word cursor-location start end save)
"The Emacs popup menu."
(if (and (not event)
(display-mouse-p))
@@ -2230,32 +2707,106 @@ If OPOINT is non-nil, restore point there after adjusting it for replacement."
(1+ (cdr (cdr mouse-pos))))
(car mouse-pos)))))
(let* ((corrects (if flyspell-sort-corrections
- (sort (car (cdr (cdr poss))) 'string<)
- (car (cdr (cdr poss)))))
- (cor-menu (if (consp corrects)
- (mapcar (lambda (correct)
- (list correct correct))
- corrects)
- '()))
+ ;; reverse word order for proper menu display
+ (reverse (sort (car (cdr (cdr poss))) 'string<))
+ (reverse (car (cdr (cdr poss))))))
(affix (car (cdr (cdr (cdr poss)))))
- show-affix-info
- (base-menu (let ((save (if (and (consp affix) show-affix-info)
- (list
- (list (concat "Save affix: " (car affix))
- 'save)
- '("Accept (session)" session)
- '("Accept (buffer)" buffer))
- '(("Save word" save)
- ("Accept (session)" session)
- ("Accept (buffer)" buffer)))))
- (if (consp cor-menu)
- (append cor-menu (cons "" save))
- save)))
- (menu (cons "flyspell correction menu" base-menu)))
- (car (x-popup-menu event
- (list (format "%s [%s]" word (or ispell-local-dictionary
- ispell-dictionary))
- menu)))))
+ show-affix-info)
+
+ (setq flyspell-context-menu-map
+ (make-sparse-keymap
+ (if (string= ispell-program-name "NSSpellChecker")
+ (if (not (car (cdr (cdr poss))))
+ "No Guesses Found"
+ (format "%s" word))
+ (format "%s [%s]" word (or ispell-local-dictionary
+ ispell-dictionary)))))
+
+ (define-key flyspell-context-menu-map [flyspell-corr-sep2]
+ ;; only include this separator when we'll also have more menu below it
+ `(menu-item "--" 'ignore :visible (not (eq
+ osx-key-mode-mouse-3-behavior
+ 'mouse-save-then-kill))))
+ (define-key flyspell-context-menu-map [buffer]
+ `(menu-item (if (string= ispell-program-name "NSSpellChecker")
+ "Ignore Spelling & Comment Buffer"
+ "Accept (buffer)")
+ (lambda () (interactive)
+ (flyspell-do-correct
+ 'buffer
+ ',poss
+ ,word
+ ,cursor-location
+ ,start
+ ,end
+ ,save))
+ :help "Always consider spelling as correct in this buffer"))
+ (define-key flyspell-context-menu-map [session]
+ `(menu-item (if (string= ispell-program-name "NSSpellChecker")
+ "Ignore Spelling"
+ "Accept (session)")
+ (lambda () (interactive)
+ (flyspell-do-correct
+ 'session
+ ',poss
+ ,word
+ ,cursor-location
+ ,start
+ ,end
+ ,save))
+ :help (if (string= ispell-program-name "NSSpellChecker")
+ "Consider spelling as correct in this buffer and session"
+ "Consider spelling correct for buffers in current session")))
+ (define-key flyspell-context-menu-map [save]
+ `(menu-item (if (string= ispell-program-name "NSSpellChecker")
+ "Learn Spelling"
+ "Save word")
+ (lambda () (interactive)
+ (flyspell-do-correct
+ 'save
+ ',poss
+ ,word
+ ,cursor-location
+ ,start
+ ,end
+ ,save))
+ :help "Save spelling to dictionary"))
+ (define-key flyspell-context-menu-map [affix]
+ ;; affix is nil for NSSpellChecker, so not visible
+ `(menu-item (concat "Save affix: " ,(car affix))
+ (lambda () (interactive)
+ (flyspell-do-correct
+ 'save
+ ',poss
+ ,word
+ ,cursor-location
+ ,start
+ ,end
+ ,save))
+ :help "Save affix construction to dictionary"
+ :visible (and ,(consp affix) ,show-affix-info)))
+ (when (consp corrects)
+ (define-key flyspell-context-menu-map [flyspell-corr-sep] '(menu-item "--"))
+ (let ((count 0)
+ cor-count-str)
+ (mapc (lambda (correct)
+ ;; generate custom symbol to serve as each key
+ (setq count (1+ count)
+ cor-count-str (concat "correct" (number-to-string count)))
+ (define-key flyspell-context-menu-map `[,(intern cor-count-str)]
+ `(menu-item ,correct
+ (lambda () (interactive)
+ (flyspell-do-correct
+ ,correct
+ ',poss
+ ,word
+ ,cursor-location
+ ,start
+ ,end
+ ,save))
+ :help "Replace word with guess")))
+ corrects)))
+ (popup-menu flyspell-context-menu-map event nil)))
;;*---------------------------------------------------------------------*/
;;* flyspell-xemacs-popup ... */
diff --git a/lisp/textmodes/ispell.el b/lisp/textmodes/ispell.el
index 0cedf86..f6b7b36 100644
--- a/lisp/textmodes/ispell.el
+++ b/lisp/textmodes/ispell.el
@@ -4,6 +4,7 @@
;; Author: Ken Stevens <k.stevens@ieee.org>
;; Maintainer: Ken Stevens <k.stevens@ieee.org>
+;; in Aquamacs: Nathaniel Cunningham <nathaniel.cunningham@gmail.com>
;; Stevens Mod Date: Mon Jan 7 12:32:44 PST 2003
;; Stevens Revision: 3.6
;; Status : Release with 3.1.12+ and 3.2.0+ ispell.
@@ -11,7 +12,7 @@
;; Web Site : http://kdstevens.com/~stevens/ispell-page.html
;; Keywords: unix wp
-;; This file is part of GNU Emacs.
+;; This file is part of GNU Emacs and of Aquamacs Emacs.
;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
@@ -350,11 +351,14 @@ Must be greater than 1."
:type 'integer
:group 'ispell)
+;;;###autoload
(defcustom ispell-program-name
- (or (executable-find "aspell")
- (executable-find "ispell")
- (executable-find "hunspell")
- "ispell")
+;; set NSSpellChecker as the default; no need to see if it's installed
+ "NSSpellChecker"
+ ;; (or (executable-find "aspell")
+ ;; (executable-find "ispell")
+ ;; (executable-find "hunspell")
+ ;; "ispell")
"Program invoked by \\[ispell-word] and \\[ispell-region] commands."
:type 'string
:set (lambda (symbol value)
@@ -532,6 +536,10 @@ is automatically set when defined in the file with either
(const :tag "default" nil))
:group 'ispell)
+(defvar ispell-dictionary-internal ispell-dictionary
+ "Internal copy of `ispell-dictionary'; can be modified by NSSpellChecker
+panel without disturbing `ispell-dictionary' setting.")
+
(defcustom ispell-extra-args nil
"If non-nil, a list of extra switches to pass to the Ispell program.
For example, (\"-W\" \"3\") to cause it to accept all 1-3 character
@@ -817,6 +825,459 @@ here just for backwards compatibility.")
"[[:alpha:]] if Emacs supports [:alpha:] regexp, nil
otherwise (current XEmacs does not support it).")
+;; **********************************************************************
+;; settings to control the use of NSSpellChecker as the spellchecking
+;; engine, instead of ispell/aspell/hunspell
+
+(defvar ns-spellchecker-language-alist
+'(("es" . "castellano")
+ ("da" . "dansk")
+ ("de" . "deutsch")
+ ("en" . "english")
+ ("fr" . "francais")
+ ("it" . "italiano")
+ ("nl" . "nederlands")
+ ("pt" . "portugues")
+ ("ru" . "russian")
+ ("sv" . "svenska"))
+ "alist pairing NSSpellChecker languages with corresponding entries
+in ispell's default ispell-dictionary-alist. This is only for the
+purposes of extracting `otherchars' and `many-otherchars-p'")
+
+(defun ns-spellchecker-ispell-equiv-language (language)
+ "Returns the name of the language from
+ispell-dictionary-base-alist that corresponds to LANGUAGE
+of NSSpellChecker. Returns nil if no corresponding language found."
+ (let ((lang-short-abbrev (substring language 0 2))
+ (lang-abbrev-p (or (equal (length language) 2)
+ (equal (substring language 2 3) "_"))))
+ (if lang-abbrev-p
+ (cdr (assoc lang-short-abbrev ns-spellchecker-language-alist)))))
+
+(defun ns-spellchecker-dictionary-otherchars (language)
+ "Returns the pair (otherchars . many-otherchars-p) for the specified
+NSSpellChecker LANGUAGE, by finding the corresponding entries in
+ispell-dictionary-base-alist. If no corresponding entry is found, assumes
+' for otherchars and t for many-otherchars-p."
+ (let* ((ispell-language (ns-spellchecker-ispell-equiv-language language))
+ (ispell-lang-list (assoc ispell-language ispell-dictionary-base-alist))
+ otherchars
+ many-otherchars-p)
+ (if ispell-language
+ (setq otherchars
+ (car (cdr (cdr (cdr ispell-lang-list))))
+ many-otherchars-p
+ (car (cdr (cdr (cdr (cdr ispell-lang-list))))))
+ (setq otherchars "[']"
+ many-otherchars-p t))
+ (cons otherchars many-otherchars-p)))
+
+(defun ns-spellchecker-dictionary-details (language)
+ (let* ((otherchars-pair (ns-spellchecker-dictionary-otherchars language))
+ (otherchars (car otherchars-pair))
+ (many-otherchars-p (cdr otherchars-pair)))
+ (list
+ language
+ "[[:alpha:]]"
+ "[^[:alpha:]]"
+ otherchars
+ many-otherchars-p
+ nil
+ nil
+ 'iso-8859-1)))
+
+(defun ns-spellchecker-list-dictionaries ()
+ (let ((lang-list (ns-spellchecker-list-languages))
+ dictionary-list)
+ ;; if (ns-spellchecker-current-language) returns a language not
+ ;; included by (ns-spellchecker-list-languages) --
+ ;; e.g. "Multilingual" in OS 10.6 -- append it to the list.
+ (add-to-list 'lang-list (ns-spellchecker-current-language))
+ (dolist (lang lang-list)
+ (setq dictionary-list
+ (cons (ns-spellchecker-dictionary-details lang)
+ dictionary-list)))
+ dictionary-list))
+
+(defcustom ns-spellchecker-chunk-size 100000
+ "approximate size in characters of the chunks of text to be
+passed to `ns-spellchecker-check-spelling' when checking large
+regions."
+ :type '(choice (const :tag "Default" 100000)
+ number ))
+
+(defun ns-spellchecker-parse-output (word)
+ "NSSpellChecker replacement for ispell-parse-output. Spellcheck WORD
+and Return:
+1: t for an exact match.
+2: A list of possible correct spellings of the format:
+ (\"ORIGINAL-WORD\" OFFSET MISS-LIST)
+ ORIGINAL-WORD is a string of the possibly misspelled word.
+ OFFSET is an integer giving the character offset of the word from
+ the beginning of the line.
+ MISS-LIST is a possibly null list of guesses."
+ (unless (string= ispell-current-dictionary
+ (ns-spellchecker-current-language))
+ (ispell-change-dictionary (ns-spellchecker-current-language)))
+ (let* ((output (ns-spellchecker-check-spelling word (current-buffer)))
+ (offset (car output)))
+ (if offset
+ ;; word is incorrect -- return
+ ;; (\"ORIGINAL-WORD\" OFFSET MISS-LIST GUESS-LIST)
+ ;; GUESS-LIST built from known affixes is nil for NSSpellChecker
+ (list word (1+ offset) (ns-spellchecker-get-suggestions word) nil)
+ ;; offset is nil: word is correct -- return t
+ t)))
+
+(defun ispell-ns-spellcheck-string (string)
+ "NSSpellChecker replacement for ispell-parse-output. Spellcheck STRING
+and return a list of lists (one for each misspelled word) of the format:
+ (\"ORIGINAL-WORD\" OFFSET MISS-LIST nil)
+ ORIGINAL-WORD is a string of the possibly misspelled word.
+ OFFSET is an integer giving the line offset of the word.
+ MISS-LIST is a possibly null list of guesses."
+ (unless (string= ispell-current-dictionary
+ (ns-spellchecker-current-language))
+ (ispell-change-dictionary (ns-spellchecker-current-language)))
+ (let ((strlen (length string))
+ (prev-offset 0)
+ ns-spellcheck-output
+ offset
+ length
+ word
+ return-list)
+ (while (progn
+ (setq ns-spellcheck-output
+ (ns-spellchecker-check-spelling string (current-buffer))
+ offset (car ns-spellcheck-output)
+ length (cdr ns-spellcheck-output))
+ ;; if no misspelled words, terminate while loop
+ (when offset
+ ;; misspelled word found; get word;
+ ;; set string to not-yet-checked portion;
+ ;; add details of misspelling to head of return-list
+ (setq word (substring string offset (+ offset length))
+ string (substring string (+ offset length)))
+ (add-to-list 'return-list
+ (list word (+ prev-offset offset)
+ (ns-spellchecker-get-suggestions word)
+ nil))
+ (setq prev-offset (+ prev-offset offset length))
+ )))
+ return-list))
+
+;;; **********************************************************************
+;;; settings to use cocoAspell preferences (from Spelling prefpane)
+;;; or cocoAspell-installed aspell dictionaries
+
+;; we need an alist or hash table pairing up language names and
+;; abbreviations
+(defvar ispell-language-abbrev-alist
+ (list
+ (cons "Afrikaans" "af")
+ (cons "Amharic" "am")
+ (cons "Arabic" "ar")
+ (cons "Azerbaijani" "az")
+ (cons "Belarusian" "be")
+ (cons "Bulgarian" "bg")
+ (cons "Bengali" "bn")
+ (cons "Breton" "br")
+ (cons "Catalan" "ca")
+ (cons "Czech" "cs")
+ (cons "Kashubian" "csb")
+ (cons "Welsh" "cy")
+ (cons "Danish" "da")
+ (cons "German" "de")
+ (cons "Greek" "el")
+ (cons "English" "en")
+ (cons "Esperanto" "eo")
+ (cons "Spanish" "es")
+ (cons "Estonian" "et")
+ (cons "Persian" "fa")
+ (cons "Finnish" "fi")
+ (cons "Faroese" "fo")
+ (cons "French" "fr")
+ (cons "Frisian" "fy")
+ (cons "Irish" "ga")
+ (cons "Scottish Gaelic" "gd")
+ (cons "Gallegan" "gl")
+ (cons "Gujarati" "gu")
+ (cons "Manx" "gv")
+ (cons "Hebrew" "he")
+ (cons "Hindi" "hi")
+ (cons "Hiligaynon" "hil")
+ (cons "Croatian" "hr")
+ (cons "Upper Sorbian" "hsb")
+ (cons "Hungarian" "hu")
+ (cons "Armenian" "hy")
+ (cons "Interlingua" "ia")
+ (cons "Indonesian" "id")
+ (cons "Icelandic" "is")
+ (cons "Italian" "it")
+ (cons "Kurdish" "ku")
+ (cons "Latin" "la")
+ (cons "Lithuanian" "lt")
+ (cons "Latvian" "lv")
+ (cons "Malagasy" "mg")
+ (cons "Maori" "mi")
+ (cons "Macedonian" "mk")
+ (cons "Malayalam" "ml")
+ (cons "Mongolian" "mn")
+ (cons "Marathi" "mr")
+ (cons "Malay" "ms")
+ (cons "Maltese" "mt")
+ (cons "Norwegian Bokmål" "nb")
+ (cons "Low German; Low Saxon" "nds")
+ (cons "Dutch" "nl")
+ (cons "Norwegian Nynorsk" "nn")
+ (cons "Nyanja; Chichewa; Chewa" "ny")
+ (cons "Oriya" "or")
+ (cons "Punjabi" "pa")
+ (cons "Polish" "pl")
+ (cons "Portuguese" "pt")
+ (cons "Quechua" "qu")
+ (cons "Romanian" "ro")
+ (cons "Russian" "ru")
+ (cons "Kinyarwanda" "rw")
+ (cons "Sardinian" "sc")
+ (cons "Slovak" "sk")
+ (cons "Slovenian" "sl")
+ (cons "Serbian" "sr")
+ (cons "Swedish" "sv")
+ (cons "Swahili" "sw")
+ (cons "Tamil" "ta")
+ (cons "Telugu" "te")
+ (cons "Tetum" "tet")
+ (cons "Turkmen" "tk")
+ (cons "Tagalog" "tl")
+ (cons "Tswana" "tn")
+ (cons "Turkish" "tr")
+ (cons "Ukrainian" "uk")
+ (cons "Uzbek" "uz")
+ (cons "Vietnamese" "vi")
+ (cons "Walloon" "wa")
+ (cons "Yiddish" "yi")
+ (cons "Zulu" "zu"))
+ "Paired language names and ISO abbreviations.
+Used to match cocoAspell language names to dictionary
+file names.")
+
+;; another alist for language region abbreviations
+(defvar ispell-lregion-abbrev-alist
+ (list
+ (cons "United States" "US")
+ (cons "Canada" "CA")
+ (cons "United Kingdom" "GB")
+ (cons "France" "FR")
+ (cons "Switzerland" "CH")
+ (cons "Austria" "AT")
+ (cons "Germany" "DE")
+ (cons "Brazil" "BR")
+ (cons "Portugal" "PT"))
+ "Paired language region names and abbreviations.
+Used to match cocoAspell language names to dictionary
+file names.")
+
+(defvar aspell-knows-no-dicts nil
+ "Records whether or not aspell's default configuration can
+locate any dictionaries.")
+
+(defcustom ispell-use-cocoaspell
+ 'auto
+ "Specify whether spell-checking with aspell should use
+cocoaAspell installation. The following options are available:
+- `full'
+ Use cocoAspell preferences and dictionaries.
+- `dicts'
+ Use cocoAspell dictionaries only (e.g. when
+ Spelling prefpane isn't installed).
+- nil
+ Use generic aspell without cocoAspell.
+- `auto'
+ Determine the proper setting at runtime,
+ based on detected aspell/cocoAspell installation."
+ :group 'ispell
+ :type '(choice :tag "Aspell should use..."
+ (const :tag "cocoAspell Preferences and Dictionaries" full)
+ (const :tag "cocoAspell Dictionaries Only" dicts)
+ (const :tag "generic install (Don't use cocoAspell)" nil)
+ (const :tag "automatic setting" auto)))
+
+(defvar ispell-use-cocoaspell-internal nil
+ "Internal value based on `ispell-use-cocoaspell', after checking
+aspell/cocoaspell installation if needed. Do not set directly.")
+
+(defvar ispell-cocoaspell-prefs-dir nil
+ "Full path to cocoAspell's preferences directory, if cocoAspell installed")
+
+(defvar ispell-cocoaspell-prefpane-plist nil
+ "Full path to file recording cocoAspell Spelling prefpane's preferences.
+File contents are used to determine which languages are available for
+spell checking.")
+
+(defvar ispell-cocoaspell-dict-list nil
+ "List of dictionaries checked in cocoAspell's Spelling prefpane.")
+
+(defvar ispell-cocoaspell-dict-dir-list nil
+ "List of paths of directories for all cocoAspell-installed
+aspell dictionaries.")
+
+(defvar ispell-cocoaspell-dict-aliases nil
+ "List of base filenames of all *.alias files in cocoAspell-installed aspell
+dictionary directories. These will be used to generate ispell-dictionary-alist
+when cocoAspell dictionaries are installed, but no Spelling prefpane.")
+
+(defun ispell-update-cocoaspell-settings ()
+ (setq aspell-knows-no-dicts
+ (condition-case nil
+ (with-temp-buffer
+ ;; is there a stored cocoaSpell configuration?
+ (call-process ispell-program-name nil t nil "dicts")
+ (eq (point-min) (point-max)))
+ (error nil)))
+
+ ;; determine how to configure aspell
+ (setq ispell-use-cocoaspell-internal
+ (if (eq ispell-use-cocoaspell 'auto)
+ ;; automatically determine appropriate setting
+ (when aspell-knows-no-dicts
+ ;; assume cocoaspell if no dictionaries known to aspell
+ (if (file-accessible-directory-p
+ (expand-file-name "~/Library/Services/cocoAspell.service/"))
+ ;; if cocoaSpell service is installed, use full cocoaspell support
+ 'full
+ ;; else just use cocoaSpell-installed dictionaries
+ 'dicts))
+ ;; use setting as specified by user
+ ispell-use-cocoaspell))
+
+ (setq ispell-cocoaspell-prefs-dir
+ (when (eq ispell-use-cocoaspell-internal 'full)
+ (expand-file-name "~/Library/Preferences/cocoAspell/")))
+
+ (setq ispell-cocoaspell-prefpane-plist
+ (when (eq ispell-use-cocoaspell-internal 'full)
+ ;; overall preferences set in Spelling prefpane
+ (expand-file-name
+ "~/Library/Services/cocoAspell.service/Contents/Info.plist")))
+
+ (setq ispell-cocoaspell-dict-list
+ (when (eq ispell-use-cocoaspell-internal 'full)
+ (let ((lang-list))
+ (with-temp-buffer
+ ;; extract language names selected in Spelling prefpane
+ (insert-file-contents ispell-cocoaspell-prefpane-plist)
+ ;; (call-process "plutil" nil t t "-convert" "xml1" "-o" "-"
+ ;; ispell-cocoaspell-prefpane-plist)
+ ;; (goto-char (point-min))
+ ;; move point to just before first language name
+ (re-search-forward "<key>NSLanguages</key>\\s +<array>\\s +<string>")
+ (while (progn
+ ;; find each language name, and move point to just before next
+ ;; tag after current string
+ (re-search-forward "\\(.+\\)</string>\\s +<\\([/a-z]+\\)>")
+ ;; tack current language onto lang-list
+ (add-to-list 'lang-list (match-string 1))
+ ;; continue if next tag indicates another language string
+ (equal (match-string 2) "string")))
+ ;; return the language list
+ lang-list))))
+
+ (setq ispell-cocoaspell-dict-dir-list
+ (when (eq ispell-use-cocoaspell-internal 'dicts)
+ (file-expand-wildcards
+ "/Library/Application Support/cocoAspell/aspell*-*-*")))
+
+ (setq ispell-cocoaspell-dict-aliases
+ (when (eq ispell-use-cocoaspell-internal 'dicts)
+ (mapcar (lambda (dict-alias)
+ (file-name-sans-extension
+ (file-name-nondirectory dict-alias)))
+ (file-expand-wildcards
+ "/Library/Application Support/cocoAspell/aspell*-*-*/*.multi"))))
+ )
+ ;; convert from names e.g. English (United States) to en_US
+ ;; append stuff in brackets
+ ;; also include length if it exists
+(defun aspell-dict-abbrev (langstring)
+ "Given LANGSTRING, a string name for a cocoAspell dictionary, return
+abbreviated dict name, which cocoAspell uses as base filename for that
+dictionary's files. e.g., passed `English (United States) [w_accents]'
+return 'en_US-w_accents'."
+ (unless (null langstring)
+ (let* ((lang-elts
+ ;; parse out: language
+ (string-match (concat "\\([[:alpha:]]+;?\\( [[:alpha:]]+;?\\)*\\)"
+ ;; region
+ "\\( (\\([[:alpha:] ]+\\))\\)?"
+ ;; modifiers
+ "\\( \\[\\(.+\\)\\]\\)?"
+ ) langstring))
+ (lang (match-string 1 langstring))
+ (lregion (match-string 4 langstring))
+ (mods (match-string 6 langstring))
+ (lang-abbrev (cdr (assoc lang ispell-language-abbrev-alist)))
+ (lregion-abbrev (or (cdr (assoc lregion ispell-lregion-abbrev-alist))
+ lregion))) ;; if no abbrev in alist, keep name
+ (if lregion-abbrev (setq lregion-abbrev (concat "_" lregion-abbrev)))
+ (if mods (setq mods (concat "-" mods)))
+ (concat lang-abbrev lregion-abbrev mods)))) ;; generate aspell conf filename
+
+(defun ispell-cocoaspell-dict-dir (dict-abbrev)
+ "Return the full path of the directory in which cocoAspell dictionary
+files for dictionary DICT-ABBREV are located."
+ (let ((filename (concat ispell-cocoaspell-prefs-dir dict-abbrev ".conf")))
+ (if (file-readable-p filename)
+ (with-temp-buffer
+ (insert-file-contents filename)
+ (when (search-forward-regexp "^dict-dir " nil t)
+ (buffer-substring (point) (progn (end-of-line) (point)))))
+ (let* ((dict-abbrev-parts (split-string dict-abbrev "-"))
+ (dict-abbrev-root (car dict-abbrev-parts))
+ (dict-abbrev-mods (nth 1 dict-abbrev-parts))
+ (dict-abbrev-root-parts (split-string dict-abbrev-root "_"))
+ (dict-lang-abbrev (car dict-abbrev-root-parts))
+ (dict-region-abbrev (nth 1 dict-abbrev-root-parts))
+ (dict-parent-dir "/Library/Application Support/cocoAspell/")
+ (dict-dir (car (or (directory-files
+ dict-parent-dir
+ nil
+ (concat "aspell[0-9]?-"
+ dict-lang-abbrev "-"
+ dict-abbrev-mods "-.+"))
+ (directory-files
+ dict-parent-dir
+ nil
+ (concat "aspell[0-9]?-"
+ dict-lang-abbrev "-"
+ dict-region-abbrev "-.+"))
+ (directory-files
+ dict-parent-dir
+ nil
+ (concat "aspell[0-9]?-"
+ dict-lang-abbrev "-.+"))))))
+ (concat dict-parent-dir dict-dir)))))
+
+(defun ispell-cocoaspell-aspell-args (dict-abbrev)
+ "Return the arguments to be passed to the aspell command to use dictionary
+DICT-ABBREV."
+ (let* ((lang-conf (concat ispell-cocoaspell-prefs-dir dict-abbrev ".conf"))
+ (readable (file-readable-p lang-conf)))
+ (if readable
+ (list (concat "--conf=" ispell-cocoaspell-prefs-dir "filters.conf")
+ (concat "--per-conf=" ispell-cocoaspell-prefs-dir dict-abbrev ".conf"))
+ (let* ((dict-abbrev-parts (split-string dict-abbrev "-"))
+ (dict-abbrev-root (car dict-abbrev-parts))
+ (dict-abbrev-mods (nth 1 dict-abbrev-parts))
+ (dict-dir (ispell-cocoaspell-dict-dir dict-abbrev)))
+ (list (concat "--dict-dir=" dict-dir)
+ (concat "--home-dir=" ispell-cocoaspell-prefs-dir)
+ (concat "--jargon=" dict-abbrev-mods)
+ (concat "--lang=" dict-abbrev-root)
+ (concat "--personal=" dict-abbrev-root ".pws")
+ (concat "--repl=" dict-abbrev-root ".prepl"))))))
+
+
;;; **********************************************************************
;;; The following are used by ispell, and should not be changed.
;;; **********************************************************************
@@ -845,6 +1306,15 @@ Otherwise returns the library directory name, if that is defined."
;; all versions, since versions earlier than 3.0.09 didn't identify
;; themselves on startup.
(interactive "p")
+ (if (string= ispell-program-name "NSSpellChecker")
+ ;; If using NSSpellChecker, just initialize variables and return.
+ (progn
+ ;; Make sure these variables are (re-)initialized to the default value
+ (setq ispell-really-aspell nil
+ ispell-aspell-supports-utf8 nil
+ ispell-really-hunspell nil
+ ispell-encoding8-command nil)
+ t)
(let ((default-directory (or (and (boundp 'temporary-file-directory)
temporary-file-directory)
default-directory))
@@ -926,7 +1396,7 @@ Otherwise returns the library directory name, if that is defined."
(if (ispell-check-minver hunspell8-minver ispell-really-hunspell)
(setq ispell-encoding8-command "-i")
(setq ispell-really-hunspell nil))))))
- result))
+ result)))
(defun ispell-call-process (&rest args)
"Like `call-process' but defend against bad `default-directory'."
@@ -1000,7 +1470,7 @@ and added as a submenu of the \"Edit\" menu.")
(defvar ispell-async-processp (and (fboundp 'delete-process)
(fboundp 'process-send-string)
(fboundp 'accept-process-output)
- ;;(fboundp 'make-process)
+ ;;(fboundp 'start-process)
;;(fboundp 'set-process-filter)
;;(fboundp 'process-kill-without-query)
)
@@ -1013,14 +1483,19 @@ and added as a submenu of the \"Edit\" menu.")
Internal use.")
(defun ispell-find-aspell-dictionaries ()
- "Find Aspell's dictionaries, and record in `ispell-dictionary-alist'."
+ "Find Aspell's dictionaries, and record in `ispell-aspell-dictionary-alist'."
(unless (and ispell-really-aspell ispell-encoding8-command)
(error "This function only works with Aspell >= 0.60"))
(let* ((dictionaries
- (split-string
+ (cond
+ ((eq ispell-use-cocoaspell-internal 'full)
+ ispell-cocoaspell-dict-list)
+ ((eq ispell-use-cocoaspell-internal 'dicts)
+ ispell-cocoaspell-dict-aliases)
+ (t (split-string
(with-temp-buffer
(ispell-call-process ispell-program-name nil t nil "dicts")
- (buffer-string))))
+ (buffer-string))))))
;; Search for the named dictionaries.
(found
(delq nil
@@ -1030,13 +1505,30 @@ Internal use.")
(setq found (ispell-aspell-add-aliases found))
;; Merge into FOUND any elements from the standard ispell-dictionary-base-alist
;; which have no element in FOUND at all.
- (dolist (dict ispell-dictionary-base-alist)
- (unless (assoc (car dict) found)
- (setq found (nconc found (list dict)))))
+ ;; ==SKIP this step -- don't add entries corresponding to dicts we don't have!!!==
+;; (dolist (dict ispell-dictionary-base-alist)
+;; (unless (assoc (car dict) found)
+;; (setq found (nconc found (list dict)))))
(setq ispell-aspell-dictionary-alist found)
;; Add a default entry
- (let ((default-dict
- '(nil "[[:alpha:]]" "[^[:alpha:]]" "[']" nil ("-B") nil utf-8)))
+ (let* ((english-dict-cdr
+ (if ispell-use-cocoaspell-internal
+ (or
+ (assoc-default "^[Ee]nglish$" ispell-aspell-dictionary-alist
+ (lambda (dict-name key)
+ (string-match key dict-name)))
+ (assoc-default "^[Ee]n$" ispell-aspell-dictionary-alist
+ (lambda (dict-name key)
+ (string-match key dict-name)))
+ (assoc-default "^[Ee]ngl.*" ispell-aspell-dictionary-alist
+ (lambda (dict-name key)
+ (and (string-match key dict-name)
+ (not (string-match ".*variant.*"
+ dict-name))))))
+ (cdr (assoc "en" ispell-aspell-dictionary-alist))))
+ (default-dict
+ (cons nil (or english-dict-cdr
+ (cdr (car ispell-aspell-dictionary-alist))))))
(push default-dict ispell-aspell-dictionary-alist))))
(defvar ispell-aspell-data-dir nil
@@ -1066,26 +1558,39 @@ of `ispell-dictionary-base-alist' elements."
(or ispell-aspell-data-dir
(setq ispell-aspell-data-dir
(ispell-get-aspell-config-value "data-dir")))
-
- ;; Try finding associated datafile. aspell will look for master .dat
- ;; file in `dict-dir' and `data-dir'. Associated .dat files must be
- ;; in the same directory as master file.
- (let ((data-file
- (catch 'datafile
- (dolist ( tmp-path (list ispell-aspell-dict-dir
- ispell-aspell-data-dir ))
- ;; Try xx.dat first, strip out variant, country code, etc,
- ;; then try xx_YY.dat (without stripping country code),
- ;; then try xx-alt.dat, for de-alt etc.
- (dolist (tmp-regexp (list "^[[:alpha:]]+"
- "^[[:alpha:]_]+"
- "^[[:alpha:]]+-\\(alt\\|old\\)"))
- (let ((fullpath
- (concat tmp-path "/"
- (and (string-match tmp-regexp dict-name)
- (match-string 0 dict-name)) ".dat")))
- (if (file-readable-p fullpath)
- (throw 'datafile fullpath)))))))
+ ;; Try finding associated datafile
+ (let* ((dict-abbrev (when (eq ispell-use-cocoaspell-internal 'full)
+ (aspell-dict-abbrev dict-name)))
+ (lang ;; Strip out region, variant, etc.
+ (and (string-match "^[[:alpha:]]+" (or dict-abbrev dict-name))
+ (match-string 0 (or dict-abbrev dict-name))))
+ ;; When using cocoaspell, need to specify dict-dir directly.
+ (dict-dir (cond
+ ((eq ispell-use-cocoaspell-internal 'full)
+ (ispell-cocoaspell-dict-dir dict-abbrev))
+ ((eq ispell-use-cocoaspell-internal 'dicts)
+ (file-name-directory
+ (car
+ (file-expand-wildcards
+ (concat
+ "/Library/Application Support/cocoAspell/aspell*-*-*/"
+ dict-name ".multi")))))))
+ (datafile0
+ (if ispell-use-cocoaspell-internal (concat dict-dir "/" lang ".dat")))
+ (datafile1
+ (concat ispell-aspell-data-dir "/" lang ".dat"))
+ (datafile2
+ (concat ispell-aspell-data-dir "/"
+ ;; Strip out anything but xx_YY.
+ (and (string-match "^[[:alpha:]_]+" dict-name)
+ (match-string 0 dict-name)) ".dat"))
+ (data-file
+ (if (and datafile0 (file-readable-p datafile0))
+ datafile0
+ (if (file-readable-p datafile1)
+ datafile1
+ (if (file-readable-p datafile2)
+ datafile2))))
otherchars)
(if data-file
@@ -1111,7 +1616,14 @@ of `ispell-dictionary-base-alist' elements."
"[^[:alpha:]]"
(regexp-opt otherchars)
t ; We can't tell, so set this to t
- (list "-d" dict-name)
+ ;; set arguments for aspell
+ ;; if we use cocoAspell prefs, use conf files associated with language
+ (cond ((eq ispell-use-cocoaspell-internal 'full)
+ (ispell-cocoaspell-aspell-args dict-abbrev))
+ ((eq ispell-use-cocoaspell-internal 'dicts)
+ (list "-d" dict-name
+ "--dict-dir" dict-dir))
+ (t (list "-d" dict-name)))
nil ; aspell doesn't support this
;; Here we specify the encoding to use while communicating with
;; aspell. This doesn't apply to command line arguments, so
@@ -1381,15 +1893,10 @@ aspell is used along with Emacs).")
(error nil))
ispell-encoding8-command
ispell-emacs-alpha-regexp)
- ;; auto-detection will only be used if spellchecker is not
- ;; ispell, supports a way to set communication to UTF-8 and
- ;; Emacs flavor supports [:alpha:]
- (if ispell-really-aspell
- (or ispell-aspell-dictionary-alist
- (ispell-find-aspell-dictionaries))
- (if ispell-really-hunspell
- (or ispell-hunspell-dictionary-alist
- (ispell-find-hunspell-dictionaries)))))
+ (progn
+ (ispell-update-cocoaspell-settings)
+ (unless ispell-aspell-dictionary-alist
+ (ispell-find-aspell-dictionaries))))
;; Substitute ispell-dictionary-alist with the list of
;; dictionaries corresponding to the given spellchecker.
@@ -1397,13 +1904,12 @@ aspell is used along with Emacs).")
;; installed dictionaries and add to it elements of the original
;; list that are not present there. Allow distro info.
(let ((found-dicts-alist
- (if (and ispell-encoding8-command
- ispell-emacs-alpha-regexp)
- (if ispell-really-aspell
- ispell-aspell-dictionary-alist
- (if ispell-really-hunspell
- ispell-hunspell-dictionary-alist))
- nil))
+ (cond ((and ispell-really-aspell
+ ispell-encoding8-command)
+ ispell-aspell-dictionary-alist)
+ ((string= ispell-program-name "NSSpellChecker")
+ (ns-spellchecker-list-dictionaries))
+ (t nil)))
(ispell-dictionary-base-alist ispell-dictionary-base-alist)
ispell-base-dicts-override-alist ; Override only base-dicts-alist
all-dicts-alist)
@@ -1460,11 +1966,18 @@ aspell is used along with Emacs).")
;; Add dicts to ``ispell-dictionary-alist'' unless already present.
(dolist (dict (append found-dicts-alist
- ispell-base-dicts-override-alist
- ispell-dictionary-base-alist))
+ ispell-base-dicts-override-alist))
+ ;; Why would we add all dictionaries to the list that already has available ones?
+ ;; ispell-dictionary-base-alist))
(unless (assoc (car dict) all-dicts-alist)
(add-to-list 'all-dicts-alist dict)))
(setq ispell-dictionary-alist all-dicts-alist))
+ (setq ispell-dictionary-internal
+ (if (string= ispell-program-name "NSSpellChecker")
+ ;; get working value of ispell-dictionary from spellingPanel
+ (ns-spellchecker-current-language)
+ ;; or set from global value
+ ispell-dictionary))
;; If Emacs flavor supports [:alpha:] use it for global dicts. If
;; spellchecker also supports UTF-8 via command-line option use it
@@ -1499,7 +2012,9 @@ The variable `ispell-library-directory' defines their location."
(ispell-set-spellchecker-params))
(let ((dicts (append ispell-local-dictionary-alist ispell-dictionary-alist))
- (dict-list (cons "default" nil))
+ (dict-list (if (string= ispell-program-name "NSSpellChecker")
+ nil
+ (cons "default" nil)))
name dict-bname)
(dolist (dict dicts)
(setq name (car dict)
@@ -1510,6 +2025,7 @@ The variable `ispell-library-directory' defines their location."
name
;; For Aspell, we already know which dictionaries exist.
(or ispell-really-aspell
+ (string= ispell-program-name "NSSpellChecker")
;; Include all dictionaries if lib directory not known.
;; Same for Hunspell, where ispell-library-directory is nil.
(not ispell-library-directory)
@@ -1520,10 +2036,97 @@ The variable `ispell-library-directory' defines their location."
(push name dict-list)))
dict-list))
+(defun toggle-text-mode-flyspell ()
+ "Toggle whether to use Flyspell in Text mode and related modes.
+This command affects all buffers that use modes related to Text mode,
+both existing buffers and buffers that you subsequently create."
+ (interactive)
+ (let ((enable-mode (not (memq 'turn-on-flyspell text-mode-hook))))
+ (if enable-mode
+ (add-hook 'text-mode-hook 'turn-on-flyspell)
+ (remove-hook 'text-mode-hook 'turn-on-flyspell))
+ (dolist (buffer (buffer-list))
+ (with-current-buffer buffer
+ (if (or (derived-mode-p 'text-mode) text-mode-variant)
+ (flyspell-mode (if enable-mode 1 0)))))
+ (message "Flyspell %s in Text modes"
+ (if enable-mode "enabled" "disabled"))))
+
+;;;###autoload
+(defun menu-bar-text-mode-flyspell ()
+ (interactive)
+ (toggle-text-mode-flyspell)
+ (customize-mark-as-set 'text-mode-hook))
+
;; Define commands in menu in opposite order you want them to appear.
;;;###autoload
(if ispell-menu-map-needed
(progn
+ (defvar ispell-submenu-map (make-sparse-keymap "Ispell"))
+ (define-key ispell-submenu-map [ispell-complete-word]
+ `(menu-item ,(purecopy "Complete Word") ispell-complete-word
+ :help ,(purecopy "Complete word at cursor using dictionary")))
+ (define-key ispell-submenu-map [ispell-complete-word-interior-frag]
+ `(menu-item ,(purecopy "Complete Word Fragment")
+ ispell-complete-word-interior-frag
+ :help ,(purecopy "Complete word fragment at cursor")))
+ (define-key ispell-submenu-map [ispell-continue]
+ `(menu-item ,(purecopy "Continue Spell-Checking") ispell-continue
+ :enable (and (boundp 'ispell-region-end)
+ (marker-position ispell-region-end)
+ (equal (marker-buffer ispell-region-end)
+ (current-buffer)))
+ :help ,(purecopy "Continue spell checking last region")))
+ (define-key ispell-submenu-map [ispell-word]
+ `(menu-item ,(purecopy "Spell-Check Word") ispell-word
+ :help ,(purecopy "Spell-check word at cursor")))
+ (define-key ispell-submenu-map [ispell-comments-and-strings]
+ `(menu-item ,(purecopy "Spell-Check Comments") ispell-comments-and-strings
+ :help ,(purecopy "Spell-check only comments and strings")))
+ (define-key ispell-submenu-map [ispell-region]
+ `(menu-item ,(purecopy "Spell-Check Region") ispell-region
+ :enable mark-active
+ :help ,(purecopy "Spell-check text in marked region")))
+ (define-key ispell-submenu-map [ispell-message]
+ `(menu-item ,(purecopy "Spell-Check Message") ispell-message
+ :visible (eq major-mode 'mail-mode)
+ :help ,(purecopy "Skip headers and included message text")))
+ (define-key ispell-submenu-map [ispell-buffer]
+ `(menu-item ,(purecopy "Spell-Check Buffer") ispell-buffer
+ :help ,(purecopy "Check spelling of selected buffer")))
+ ;;(put 'ispell-region 'menu-enable 'mark-active)
+
+ (fset 'ispell-submenu-map (symbol-value 'ispell-submenu-map))))
+
+;;;###autoload
+(if ispell-menu-map-needed
+ (progn
+ (defvar flyspell-modes-submenu-map (make-sparse-keymap "Flyspell modes"))
+ (define-key flyspell-modes-submenu-map [flyspell-no-modes]
+ `(menu-item ,(purecopy "No Modes") flyspell-no-modes
+ :button (:radio . (and (not (memq 'turn-on-flyspell
+ text-mode-hook))
+ (not (bound-and-true-p
+ global-flyspell-mode))))
+ :help ,(purecopy "Turn off automatic spellchecking by mode")))
+ (define-key flyspell-modes-submenu-map [flyspell-text-modes]
+ `(menu-item ,(purecopy "Text Modes") flyspell-text-modes
+ :button (:radio . (and (memq 'turn-on-flyspell text-mode-hook)
+ (not (bound-and-true-p
+ global-flyspell-mode))))
+ :help ,(purecopy "Turn on flyspell in text modes only")))
+ (define-key flyspell-modes-submenu-map [flyspell-all-modes]
+ `(menu-item ,(purecopy "All Modes") flyspell-all-modes
+ :button (:radio . (bound-and-true-p global-flyspell-mode))
+ :help ,(purecopy "Turn on flyspell in all modes")))
+
+ (fset 'flyspell-modes-submenu-map
+ (symbol-value 'flyspell-modes-submenu-map))))
+
+;;; define commands in menu in opposite order you want them to appear.
+;;;###autoload
+(if ispell-menu-map-needed
+ (progn
(setq ispell-menu-map (make-sparse-keymap "Spell"))
(define-key ispell-menu-map [ispell-change-dictionary]
`(menu-item ,(purecopy "Change Dictionary...") ispell-change-dictionary
@@ -1533,10 +2136,12 @@ The variable `ispell-library-directory' defines their location."
(lambda () (interactive) (ispell-kill-ispell nil 'clear))
:enable (and (boundp 'ispell-process) ispell-process
(eq (ispell-process-status) 'run))
+ :visible (not (string= ispell-program-name "NSSpellChecker"))
:help ,(purecopy "Terminate Ispell subprocess")))
(define-key ispell-menu-map [ispell-pdict-save]
`(menu-item ,(purecopy "Save Dictionary")
(lambda () (interactive) (ispell-pdict-save t t))
+ :visible (not (string= ispell-program-name "NSSpellChecker"))
:help ,(purecopy "Save personal dictionary")))
(define-key ispell-menu-map [ispell-customize]
`(menu-item ,(purecopy "Customize...")
@@ -1547,17 +2152,35 @@ The variable `ispell-library-directory' defines their location."
`(menu-item ,(purecopy "Help")
(lambda () (interactive) (describe-function 'ispell-help))
:help ,(purecopy "Show standard Ispell keybindings and commands")))
+
+ (define-key ispell-menu-map [ispell-submenu]
+ `(menu-item ,(purecopy "Ispell") ,ispell-submenu-map
+ :visible (string= ispell-program-name "NSSpellChecker")))
+ (define-key ispell-menu-map [spellcheck-menu-separator]
+ `(menu-item ,(purecopy "--")))
+
+ (define-key ispell-menu-map [flyspell-modes-submenu]
+ `(menu-item ,(purecopy "Check Spelling While Typing in Modes")
+ ,flyspell-modes-submenu-map))
(define-key ispell-menu-map [flyspell-mode]
- `(menu-item ,(purecopy "Automatic spell checking (Flyspell)")
+ `(menu-item ,(purecopy "Check Spelling While Typing (in this buffer)")
flyspell-mode
:help ,(purecopy "Check spelling while you edit the text")
+ ;; allow toggling regardless of mode-related flyspell settings
:button (:toggle . (bound-and-true-p flyspell-mode))))
+ (define-key ispell-menu-map [flyspell-buffer]
+ `(menu-item ,(purecopy "Mark All Misspellings")
+ flyspell-buffer
+ :help ,(purecopy "Scan full buffer for misspellings")))
+
(define-key ispell-menu-map [ispell-complete-word]
`(menu-item ,(purecopy "Complete Word") ispell-complete-word
+ :visible (not (string= ispell-program-name "NSSpellChecker"))
:help ,(purecopy "Complete word at cursor using dictionary")))
(define-key ispell-menu-map [ispell-complete-word-interior-frag]
`(menu-item ,(purecopy "Complete Word Fragment")
ispell-complete-word-interior-frag
+ :visible (not (string= ispell-program-name "NSSpellChecker"))
:help ,(purecopy "Complete word fragment at cursor")))))
;;;###autoload
@@ -1569,13 +2192,15 @@ The variable `ispell-library-directory' defines their location."
(marker-position ispell-region-end)
(equal (marker-buffer ispell-region-end)
(current-buffer)))
+ :visible (not (string= ispell-program-name "NSSpellChecker"))
:help ,(purecopy "Continue spell checking last region")))
(define-key ispell-menu-map [ispell-word]
`(menu-item ,(purecopy "Spell-Check Word") ispell-word
+ :visible (not (string= ispell-program-name "NSSpellChecker"))
:help ,(purecopy "Spell-check word at cursor")))
(define-key ispell-menu-map [ispell-comments-and-strings]
- `(menu-item ,(purecopy "Spell-Check Comments")
- ispell-comments-and-strings
+ `(menu-item ,(purecopy "Spell-Check Comments") ispell-comments-and-strings
+ :visible (not (string= ispell-program-name "NSSpellChecker"))
:help ,(purecopy "Spell-check only comments and strings")))))
;;;###autoload
@@ -1584,15 +2209,35 @@ The variable `ispell-library-directory' defines their location."
(define-key ispell-menu-map [ispell-region]
`(menu-item ,(purecopy "Spell-Check Region") ispell-region
:enable mark-active
+ :visible (not (string= ispell-program-name "NSSpellChecker"))
:help ,(purecopy "Spell-check text in marked region")))
(define-key ispell-menu-map [ispell-message]
`(menu-item ,(purecopy "Spell-Check Message") ispell-message
- :visible (eq major-mode 'mail-mode)
+ :visible (and (eq major-mode 'mail-mode)
+ (not (string= ispell-program-name
+ "NSSpellChecker")))
:help ,(purecopy "Skip headers and included message text")))
(define-key ispell-menu-map [ispell-buffer]
- `(menu-item ,(purecopy "Spell-Check Buffer") ispell-buffer
+ `(menu-item ,(purecopy "Spell-Check Buffer") spellcheck-now
+ :visible (not (string= ispell-program-name "NSSpellChecker"))
:help ,(purecopy "Check spelling of selected buffer")))
;;(put 'ispell-region 'menu-enable 'mark-active)
+
+ (define-key ispell-menu-map [nsspellchecker-panel-hide]
+ `(menu-item ,(purecopy "Hide Spelling Panel") spellchecker-panel-or-ispell
+ :visible (and (string= ispell-program-name "NSSpellChecker")
+ (ns-spellchecker-panel-visible-p))
+ :help ,(purecopy "Toggle OS X spellcheck panel visibility")))
+ (define-key ispell-menu-map [nsspellchecker-panel-show]
+ `(menu-item ,(purecopy "Show Spelling Panel") spellchecker-panel-or-ispell
+ :visible (and (string= ispell-program-name "NSSpellChecker")
+ (not (ns-spellchecker-panel-visible-p)))
+ :help ,(purecopy "Toggle OS X spellcheck panel visibility")))
+ (define-key ispell-menu-map [nsspellcheck]
+ `(menu-item ,(purecopy "Spellcheck Now") spellcheck-now
+ :visible (string= ispell-program-name "NSSpellChecker")
+ :help ,(purecopy "Check spelling with OS X spellchecker")))
+
(fset 'ispell-menu-map (symbol-value 'ispell-menu-map))))
;;; XEmacs versions 19 & 20
@@ -2117,6 +2762,8 @@ quit spell session exited."
(or quietly
(message "Checking spelling of %s..."
(funcall ispell-format-word-function word)))
+ (if (string= ispell-program-name "NSSpellChecker")
+ (setq poss (ns-spellchecker-parse-output word))
(ispell-send-string "%\n") ; put in verbose mode
(ispell-send-string (concat "^" word "\n"))
;; wait until ispell has processed word
@@ -2128,7 +2775,7 @@ quit spell session exited."
(if (and ispell-filter (listp ispell-filter))
(if (> (length ispell-filter) 1)
(error "Ispell and its process have different character maps")
- (setq poss (ispell-parse-output (car ispell-filter)))))
+ (setq poss (ispell-parse-output (car ispell-filter))))))
(cond ((eq poss t)
(or quietly
(message "%s is correct"
@@ -2420,12 +3067,16 @@ Global `ispell-quit' set to start location to continue spell session."
(cond
((= char ? ) nil) ; accept word this time only
((= char ?i) ; accept and insert word into pers dict
+ (if (string= ispell-program-name "NSSpellChecker")
+ (ns-spellchecker-learn-word word)
(ispell-send-string (concat "*" word "\n"))
- (setq ispell-pdict-modified-p '(t)) ; dictionary modified!
+ (setq ispell-pdict-modified-p '(t))) ; dictionary modified!
(and (fboundp 'flyspell-unhighlight-at)
(flyspell-unhighlight-at start))
nil)
((or (= char ?a) (= char ?A)) ; accept word without insert
+ (if (string= ispell-program-name "NSSpellChecker")
+ (ns-spellchecker-ignore-word word (current-buffer))
(ispell-send-string (concat "@" word "\n"))
(add-to-list 'ispell-buffer-session-localwords word)
(and (fboundp 'flyspell-unhighlight-at)
@@ -2434,7 +3085,7 @@ Global `ispell-quit' set to start location to continue spell session."
(setq ispell-buffer-local-name (buffer-name)))
(if (null ispell-pdict-modified-p)
(setq ispell-pdict-modified-p
- (list ispell-pdict-modified-p)))
+ (list ispell-pdict-modified-p))))
(if (= char ?A) 0)) ; return 0 for ispell-add buffer-local
((or (= char ?r) (= char ?R)) ; type in replacement
(and (eq 'block ispell-highlight-p) ; refresh tty's
@@ -2524,13 +3175,17 @@ Global `ispell-quit' set to start location to continue spell session."
'block))
t) ; reselect from new choices
((= char ?u) ; insert lowercase into dictionary
+ (if (string= ispell-program-name "NSSpellChecker")
+ (ns-spellchecker-learn-word (downcase word))
(ispell-send-string (concat "*" (downcase word) "\n"))
- (setq ispell-pdict-modified-p '(t)) ; dictionary modified!
+ (setq ispell-pdict-modified-p '(t))) ; dictionary modified!
nil)
((= char ?m) ; type in what to insert
+ (if (string= ispell-program-name "NSSpellChecker")
+ (ns-spellchecker-learn-word (read-string "Insert: " word))
(ispell-send-string
(concat "*" (read-string "Insert: " word) "\n"))
- (setq ispell-pdict-modified-p '(t))
+ (setq ispell-pdict-modified-p '(t)))
(cons word nil))
((and (>= num 0) (< num count))
(if ispell-query-replace-choices ; Query replace flag
@@ -2980,9 +3635,11 @@ Optional third arg SHIFT is an offset to apply based on previous corrections."
(defun ispell-process-status ()
"Return the status of the Ispell process.
When asynchronous processes are not supported, `run' is always returned."
+ (if (string= ispell-program-name "NSSpellChecker")
+ 'run
(if ispell-async-processp
(process-status ispell-process)
- (and ispell-process 'run)))
+ (and ispell-process 'run))))
(defun ispell-start-process ()
@@ -3001,12 +3658,17 @@ Keeps argument list for future Ispell invocations for no async support."
default-directory
;; Defend against bad `default-directory'.
(expand-file-name "~/")))
+ ;; cocoAspell dict basename is abbreviated version
+ (current-dict
+ (if (eq ispell-use-cocoaspell-internal 'full)
+ (aspell-dict-abbrev ispell-current-dictionary)
+ ispell-current-dictionary))
(orig-args (ispell-get-ispell-args))
(args
(append
(if (and ispell-current-dictionary ; Not for default dict (nil)
(not (member "-d" orig-args))) ; Only define if not overridden.
- (list "-d" ispell-current-dictionary))
+ (list "-d" current-dict))
orig-args
(if ispell-current-personal-dictionary ; Use specified pers dict.
(list "-p" ispell-current-personal-dictionary))
@@ -3083,7 +3745,7 @@ Keeps argument list for future Ispell invocations for no async support."
(ispell-kill-ispell t)
(message "Starting new Ispell process %s with %s dictionary..."
ispell-program-name
- (or ispell-local-dictionary ispell-dictionary "default"))
+ (or ispell-local-dictionary ispell-dictionary-internal "default"))
(sit-for 0)
(setq ispell-library-directory (ispell-check-version)
;; Assign a non-nil value to ispell-process-directory
@@ -3203,23 +3865,26 @@ Without a prefix arg, set it \"locally\", just for this buffer.
By just answering RET you can find out what the current dictionary is."
(interactive
+ (let ((completion-ignore-case t))
(list (completing-read
"Use new dictionary (RET for current, SPC to complete): "
(and (fboundp 'ispell-valid-dictionary-list)
(mapcar 'list (ispell-valid-dictionary-list)))
nil t)
- current-prefix-arg))
+ current-prefix-arg)))
(ispell-set-spellchecker-params) ; Initialize variables and dicts alists
(unless arg (ispell-buffer-local-dict 'no-reload))
(if (equal dict "default") (setq dict nil))
;; This relies on completing-read's bug of returning "" for no match
- (cond ((equal dict "")
+ (cond ((and (equal dict nil) (string= ispell-program-name "NSSpellChecker"))
+ (setq ispell-dictionary-internal (ns-spellchecker-current-language)))
+ ((equal dict "")
(ispell-internal-change-dictionary)
(message "Using %s dictionary"
(or (and (not arg) ispell-local-dictionary)
- ispell-dictionary "default")))
+ ispell-dictionary-internal "default")))
((equal dict (or (and (not arg) ispell-local-dictionary)
- ispell-dictionary "default"))
+ ispell-dictionary-internal "default"))
;; Specified dictionary is the default already. Could reload
;; the dictionaries if needed.
(ispell-internal-change-dictionary)
@@ -3232,7 +3897,8 @@ By just answering RET you can find out what the current dictionary is."
(assoc dict ispell-dictionary-alist))
(if arg
;; set default dictionary
- (setq ispell-dictionary dict)
+ (setq ispell-dictionary dict
+ ispell-dictionary-internal dict)
;; set local dictionary
(setq ispell-local-dictionary dict)
(setq ispell-local-dictionary-overridden t))
@@ -3247,15 +3913,15 @@ By just answering RET you can find out what the current dictionary is."
"Update the dictionary and the personal dictionary used by Ispell.
This may kill the Ispell process; if so, a new one will be started
when needed."
- (let* ((dict (or ispell-local-dictionary ispell-dictionary))
- (pdict (or ispell-local-pdict ispell-personal-dictionary))
- (expanded-pdict (if pdict (expand-file-name pdict))))
+ (let ((dict (or ispell-local-dictionary ispell-dictionary-internal))
+ (pdict (or ispell-local-pdict ispell-personal-dictionary)))
(unless (and (equal ispell-current-dictionary dict)
- (equal ispell-current-personal-dictionary
- expanded-pdict))
+ (equal ispell-current-personal-dictionary pdict))
(ispell-kill-ispell t)
(setq ispell-current-dictionary dict
- ispell-current-personal-dictionary expanded-pdict))))
+ ispell-current-personal-dictionary pdict)
+ (if (string= ispell-program-name "NSSpellChecker")
+ (ns-spellchecker-set-language dict)))))
;; Avoid error messages when compiling for these dynamic variables.
(defvar ispell-start)
@@ -3620,6 +4286,8 @@ Returns the sum SHIFT due to changes in word replacements."
(if (not (numberp shift))
(setq shift 0))
;; send string to spell process and get input.
+ (if (string= ispell-program-name "NSSpellChecker")
+ (setq ispell-filter (nreverse (ispell-ns-spellcheck-string string)))
(ispell-send-string string)
(while (progn
(ispell-accept-output)
@@ -3627,11 +4295,17 @@ Returns the sum SHIFT due to changes in word replacements."
(not (string= "" (car ispell-filter)))))
;; parse all inputs from the stream one word at a time.
;; Place in FIFO order and remove the blank item.
- (setq ispell-filter (nreverse (cdr ispell-filter)))
+ (setq ispell-filter (nreverse (cdr ispell-filter))))
(while (and (not ispell-quit) ispell-filter)
;; get next word, accounting for accepted words and start shifts
- (setq poss (ispell-parse-output (car ispell-filter)
- accept-list shift))
+ (setq poss (if (string= ispell-program-name "NSSpellChecker")
+ ;; add shift to offset from ispell-ns-spellcheck-string
+ (progn
+ (setcar (cdr (car ispell-filter))
+ (+ shift (cadr (car ispell-filter))))
+ (car ispell-filter))
+ (ispell-parse-output (car ispell-filter)
+ accept-list shift)))
(if (and poss (listp poss)) ; spelling error occurred.
;; Whenever we have misspellings, we can change
;; the buffer. Keep boundaries as markers.
@@ -4255,6 +4929,9 @@ You can bind this to the key C-c i in GNUS or mail by adding to
(defun ispell-accept-buffer-local-defs ()
"Load all buffer-local information, restarting Ispell when necessary."
+ (if (string= ispell-program-name "NSSpellChecker")
+ (setq ispell-current-dictionary
+ (or ispell-local-dictionary ispell-dictionary-internal)))
(ispell-buffer-local-dict) ; May kill ispell-process.
(ispell-buffer-local-words) ; Will initialize ispell-process.
(ispell-buffer-local-parsing))
@@ -4265,7 +4942,8 @@ You can bind this to the key C-c i in GNUS or mail by adding to
Overrides the default parsing mode.
Includes LaTeX/Nroff modes and extended character mode."
;; (ispell-init-process) must already be called.
- (ispell-send-string "!\n") ; Put process in terse mode.
+ (if (not (string= ispell-program-name "NSSpellChecker"))
+ (ispell-send-string "!\n")) ; Put process in terse mode.
;; We assume all major modes with "tex-mode" in them should use latex parsing
;; When exclusively checking comments, set to raw text mode (nroff).
(if (and (not (eq 'exclusive ispell-check-comments))
@@ -4274,16 +4952,19 @@ Includes LaTeX/Nroff modes and extended character mode."
(symbol-name major-mode)))
(eq ispell-parser 'tex)))
(progn
- (ispell-send-string "+\n") ; set ispell mode to tex
+ (if (not (string= ispell-program-name "NSSpellChecker"))
+ (ispell-send-string "+\n")) ; set ispell mode to tex
(if (not (eq ispell-parser 'tex))
(set (make-local-variable 'ispell-parser) 'tex)))
- (ispell-send-string "-\n")) ; set mode to normal (nroff)
+ (if (not (string= ispell-program-name "NSSpellChecker"))
+ (ispell-send-string "-\n"))) ; set mode to normal (nroff)
;; If needed, test for SGML & HTML modes and set a buffer local nil/t value.
(if (and ispell-skip-html (not (eq ispell-skip-html t)))
(setq ispell-skip-html
(not (null (string-match "sgml\\|html\\|xml"
(downcase (symbol-name major-mode)))))))
;; Set default extended character mode for given buffer, if any.
+ (when (not (string= ispell-program-name "NSSpellChecker"))
(let ((extended-char-mode (ispell-get-extended-character-mode)))
(if extended-char-mode
(ispell-send-string (concat extended-char-mode "\n"))))
@@ -4306,7 +4987,7 @@ Includes LaTeX/Nroff modes and extended character mode."
((string-match "~" string) ; Set extended character mode.
(ispell-send-string (concat string "\n")))
(t (message "Invalid Ispell Parsing argument!")
- (sit-for 2))))))))
+ (sit-for 2)))))))))
;; Can kill the current ispell process
@@ -4354,12 +5035,13 @@ Both should not be used to define a buffer-local dictionary."
(ispell-kill-ispell t))
;; Actually start a new ispell process, because we need
;; to send commands now to specify the local words to it.
+ (if (not (string= ispell-program-name "NSSpellChecker"))
(ispell-init-process)
(dolist (session-localword ispell-buffer-session-localwords)
(ispell-send-string (concat "@" session-localword "\n")))
(or ispell-buffer-local-name
(if ispell-buffer-session-localwords
- (setq ispell-buffer-local-name (buffer-name))))
+ (setq ispell-buffer-local-name (buffer-name)))))
(save-excursion
(goto-char (point-min))
(while (search-forward ispell-words-keyword nil t)
@@ -4376,7 +5058,13 @@ Both should not be used to define a buffer-local dictionary."
;; Error handling needs to be added between ispell and Emacs.
(if (and (< 1 (length string))
(equal 0 (string-match ispell-casechars string)))
- (ispell-send-string (concat "@" string "\n"))))))))
+ (if (string= ispell-program-name "NSSpellChecker")
+ (progn
+ ;; dummy spellcheck to ensure that NSSpellChecker
+ ;; is initialized
+ (ns-spellchecker-check-spelling "test")
+ (ns-spellchecker-ignore-word string (current-buffer)))
+ (ispell-send-string (concat "@" string "\n")))))))))
;; Returns optionally adjusted region-end-point.
diff --git a/src/nsfns.m b/src/nsfns.m
index 9bc6c1d..3b4dfc6 100644
--- a/src/nsfns.m
+++ b/src/nsfns.m
@@ -1095,6 +1095,363 @@ get_geometry_from_preferences (struct ns_display_info *dpyinfo,
========================================================================== */
+/* Spelling */
+
+DEFUN ("ns-popup-spellchecker-panel", Fns_popup_spellchecker_panel, Sns_popup_spellchecker_panel,
+ 0, 0, "",
+ doc: /* Pop up the spell checking panel.
+Shows the NS spell checking panel and brings it to the front.*/)
+ (void)
+{
+ id sc;
+
+ check_window_system (NULL);
+ sc = [NSSpellChecker sharedSpellChecker];
+
+ block_input();
+ [[sc spellingPanel] orderFront: NSApp];
+
+ // Spelling panel should appear with previous content, not empty.
+ // [sc updateSpellingPanelWithMisspelledWord:@""]; // no word, no spelling errors
+
+ // found here: http://trac.webkit.org/changeset/19670
+ // // FIXME 4811447: workaround for lack of API
+ // NSSpellChecker *spellChecker = [NSSpellChecker sharedSpellChecker];
+ // does not work
+ // if ([sc respondsToSelector:@selector(_updateGrammar)])
+ // [sc performSelector:@selector(_updateGrammar)];
+ unblock_input();
+ return Qnil;
+}
+
+DEFUN ("ns-close-spellchecker-panel", Fns_close_spellchecker_panel, Sns_close_spellchecker_panel,
+ 0, 0, "",
+ doc: /* Close the spell checking panel.*/)
+ (void)
+{
+ id sc;
+
+ check_window_system (NULL);
+ sc = [NSSpellChecker sharedSpellChecker];
+
+ block_input();
+ [[sc spellingPanel] close];
+
+ unblock_input();
+ return Qnil;
+}
+
+DEFUN ("ns-spellchecker-panel-visible-p", Fns_spellchecker_panel_visible_p, Sns_spellchecker_panel_visible_p,
+ 0, 0, "",
+ doc: /* Return t if spellchecking panel is visible,
+nil otherwise.*/)
+ (void)
+{
+ id sc;
+ BOOL visible;
+
+ check_window_system (NULL);
+ sc = [NSSpellChecker sharedSpellChecker];
+
+ block_input();
+ visible = [[sc spellingPanel] isVisible];
+
+ unblock_input();
+ return visible ? Qt : Qnil;
+}
+
+
+DEFUN ("ns-spellchecker-show-word", Fns_spellchecker_show_word, Sns_spellchecker_show_word,
+ 1, 1, 0,
+ doc: /* Show word WORD in the spellchecking panel.
+Give empty string to delete word.*/)
+ (str)
+ Lisp_Object str;
+{
+ id sc;
+
+ CHECK_STRING (str);
+ check_window_system (NULL);
+ block_input();
+ sc = [NSSpellChecker sharedSpellChecker];
+
+ [sc updateSpellingPanelWithMisspelledWord:[NSString stringWithUTF8String: SDATA (str)]]; // no word, no spelling errors
+
+ unblock_input();
+ return Qnil;
+}
+
+
+DEFUN ("ns-spellchecker-learn-word", Fns_spellchecker_learn_word, Sns_spellchecker_learn_word,
+ 1, 1, 0,
+ doc: /* Learn word WORD.
+Returns learned word if successful.
+Not available on 10.4.*/)
+ (str)
+ Lisp_Object str;
+{
+ CHECK_STRING (str);
+ check_window_system (NULL);
+ block_input();
+ id sc = [NSSpellChecker sharedSpellChecker];
+
+#ifdef NS_IMPL_COCOA
+ if ([sc respondsToSelector:@selector(learnWord:)]) // (NSAppKitVersionNumber >= 824.0)
+ {
+
+ [sc learnWord:[NSString stringWithUTF8String: SDATA (str)]];
+ unblock_input();
+ return str;
+ }
+#endif
+ unblock_input();
+ return Qnil;
+}
+
+
+DEFUN ("ns-spellchecker-ignore-word", Fns_spellchecker_ignore_word, Sns_spellchecker_ignore_word,
+ 1, 2, 0,
+ doc: /* Ignore word WORD in buffer BUFFER.*/)
+ (str, buffer)
+ Lisp_Object str, buffer;
+{
+ id sc;
+
+ CHECK_STRING (str);
+ check_window_system (NULL);
+ block_input();
+ sc = [NSSpellChecker sharedSpellChecker];
+
+ NSInteger tag = 1;
+ if (! NILP (buffer))
+ {
+ tag = sxhash (buffer, 0);
+ }
+
+ [sc ignoreWord:[NSString stringWithUTF8String: SDATA (str)] inSpellDocumentWithTag:tag];
+ unblock_input();
+ return Qnil;
+}
+
+
+DEFUN ("ns-spellchecker-ignored-words", Fns_spellchecker_ignored_words, Sns_spellchecker_ignored_words,
+ 1, 1, 0,
+ doc: /* Return list of words ignored by NSSpellChecker
+for buffer BUFFER */)
+ (buffer)
+ Lisp_Object buffer;
+{
+ id sc;
+
+ check_window_system (NULL);
+ block_input();
+ sc = [NSSpellChecker sharedSpellChecker];
+
+ NSInteger tag = 1;
+ if (! NILP (buffer))
+ {
+ tag = sxhash (buffer, 0);
+ }
+
+ Lisp_Object retval = Qnil;
+ NSArray *words = [sc ignoredWordsInSpellDocumentWithTag:tag];
+ int arrayCount = [words count];
+ int i;
+ for (i = 0; i < arrayCount; i++) {
+ // build Lisp list of strings
+ retval = Fcons (build_string ([[words objectAtIndex:i] UTF8String]),
+ retval);
+ }
+ unblock_input();
+ return retval;
+}
+
+
+DEFUN ("ns-spellchecker-check-spelling", Fns_spellchecker_check_spelling, Sns_spellchecker_check_spelling,
+ 1, 2, 0,
+ doc: /* Check spelling of STRING
+Returns the location of the first misspelled word in a
+cons cell of form (beginning . length), or nil if all
+words are spelled as in the dictionary.*/)
+ (string, buffer)
+ Lisp_Object string, buffer;
+{
+ id sc;
+
+ CHECK_STRING (string);
+ check_window_system (NULL);
+ block_input();
+ sc = [NSSpellChecker sharedSpellChecker];
+
+ /* NSRange first_word = nil; // Invalid initializer! NSRange is a struct */
+ NSInteger tag = 1;
+ if (! NILP (buffer) )
+ {
+ tag = sxhash (buffer, 0);
+ }
+
+ /* unfinished -
+ if ([sc respondsToSelector:@selector(checkString:range:types:options:inSpellDocumentWithTag:orthography:wordCount:)])
+ {
+ NSString *nsString = [NSString stringWithUTF8String: SDATA (string)];
+ NSArray *spelling_result = [sc
+ checkString:nsString
+ range:NSMakeRange(0,[nsString size]-1)
+ types:NSTextCheckingAllSystemTypes - NSTextCheckingTypeGrammar
+ options:nil
+ inSpellDocumentWithTag:tag
+ orthography:nil // difficult to produce
+ wordCount:nil];
+
+ } else */
+ // {
+
+ NSRange first_word = [sc checkSpellingOfString:[NSString stringWithUTF8String: SDATA (string)] startingAt:((NSInteger) 0)
+ language:nil wrap:NO inSpellDocumentWithTag:tag wordCount:nil];
+
+ // }
+ unblock_input();
+ if (first_word.location == NSNotFound || (int) first_word.location < 0)
+ return Qnil;
+ else
+ return Fcons (make_number (first_word.location), make_number (first_word.length));
+}
+
+
+DEFUN ("ns-spellchecker-check-grammar", Fns_spellchecker_check_grammar, Sns_spellchecker_check_grammar,
+ 1, 2, 0,
+ doc: /* Check spelling of SENTENCE.
+BUFFER, if given, idenitifies the document containing list
+of ignored grammatical constructions. */)
+ (sentence, buffer)
+ Lisp_Object sentence, buffer;
+{
+ id sc;
+
+ CHECK_STRING (sentence);
+ check_window_system (NULL);
+ block_input();
+ sc = [NSSpellChecker sharedSpellChecker];
+
+ NSInteger tag = 1;
+ if (! NILP (buffer) )
+ {
+ tag = sxhash (buffer, 0);
+ }
+
+ NSArray *errdetails;
+
+ /* to do: use long version */
+ NSRange first_word = [sc checkGrammarOfString: [NSString stringWithUTF8String: SDATA (sentence)] startingAt:((NSInteger) 0)
+ language:nil wrap:NO inSpellDocumentWithTag:tag details:&errdetails];
+
+ unblock_input();
+ if (first_word.length == 0) // Is this how "no location" is indicated?
+ return Qnil;
+ else
+ return Fcons (make_number ((int) first_word.location), make_number ((int) first_word.length));
+}
+
+
+DEFUN ("ns-spellchecker-get-suggestions", Fns_spellchecker_get_suggestions, Sns_spellchecker_get_suggestions,
+ 1, 1, 0,
+ doc: /* Get suggestions for WORD.
+If word contains all capital letters, or its first
+letter is capitalized, the suggested words are
+capitalized in the same way. */)
+ (word)
+ Lisp_Object word;
+{
+ id sc;
+
+ CHECK_STRING (word);
+ check_window_system (NULL);
+ block_input();
+ sc = [NSSpellChecker sharedSpellChecker];
+
+ Lisp_Object retval = Qnil;
+ NSArray *guesses = [sc guessesForWord: [NSString stringWithUTF8String: SDATA (word)]];
+ int arrayCount = [guesses count];
+ int i = arrayCount;
+ while (--i >= 0)
+ retval = Fcons (build_string ([[guesses objectAtIndex:i] UTF8String]),
+ retval);
+ unblock_input();
+ return retval;
+}
+
+
+DEFUN ("ns-spellchecker-list-languages", Fns_spellchecker_list_languages, Sns_spellchecker_list_languages,
+ 0, 0, 0,
+ doc: /* Get all available spell-checking languages.
+Returns nil if not successful.*/)
+ (void)
+{
+ id sc;
+ Lisp_Object retval = Qnil;
+
+ check_window_system (NULL);
+ block_input();
+ sc = [NSSpellChecker sharedSpellChecker];
+
+#ifdef NS_IMPL_COCOA
+ if ([sc respondsToSelector:@selector(availableLanguages)]) // (NSAppKitVersionNumber >= 824.0)
+ {
+ NSArray *langs = [sc availableLanguages];
+ int arrayCount = [langs count];
+ int i;
+ for (i = 0; i < arrayCount; i++) {
+ // build Lisp list of strings
+ retval = Fcons (build_string ([[langs objectAtIndex:i] UTF8String]),
+ retval);
+ }
+ }
+#endif
+ unblock_input();
+ return retval;
+}
+
+
+DEFUN ("ns-spellchecker-current-language", Fns_spellchecker_current_language, Sns_spellchecker_current_language,
+ 0, 0, 0,
+ doc: /* Get the current spell-checking language.*/)
+ (void)
+{
+ id sc;
+
+ check_window_system (NULL);
+ block_input();
+ sc = [NSSpellChecker sharedSpellChecker];
+
+ Lisp_Object retval = Qnil;
+ NSString *lang = [sc language];
+ retval = build_string ([lang UTF8String]);
+
+ unblock_input();
+ return retval;
+}
+
+
+DEFUN ("ns-spellchecker-set-language", Fns_spellchecker_set_language, Sns_spellchecker_set_language,
+ 1, 1, 0,
+ doc: /* Set spell-checking language.
+LANGUAGE must be one of the languages returned by
+`ns-spellchecker-list-langauges'.*/)
+ (language)
+ Lisp_Object language;
+{
+ id sc;
+
+ CHECK_STRING (language);
+ check_window_system (NULL);
+ block_input();
+ sc = [NSSpellChecker sharedSpellChecker];
+
+ [sc setLanguage: [NSString stringWithUTF8String: SDATA (language)]];
+ unblock_input();
+ return Qnil;
+}
+
DEFUN ("x-create-frame", Fx_create_frame, Sx_create_frame,
1, 1, 0,
doc: /* Make a new Nextstep window, called a "frame" in Emacs terms.
@@ -3136,6 +3493,20 @@ be used as the image of the icon representing the frame. */);
doc: /* Toolkit version for NS Windowing. */);
Vns_version_string = ns_appkit_version_str ();
+ defsubr (&Sns_popup_spellchecker_panel);
+ defsubr (&Sns_close_spellchecker_panel);
+ defsubr (&Sns_spellchecker_panel_visible_p);
+ defsubr (&Sns_spellchecker_learn_word);
+ defsubr (&Sns_spellchecker_ignore_word);
+ defsubr (&Sns_spellchecker_ignored_words);
+ defsubr (&Sns_spellchecker_show_word);
+ defsubr (&Sns_spellchecker_check_spelling);
+ defsubr (&Sns_spellchecker_check_grammar);
+ defsubr (&Sns_spellchecker_get_suggestions);
+ defsubr (&Sns_spellchecker_list_languages);
+ defsubr (&Sns_spellchecker_current_language);
+ defsubr (&Sns_spellchecker_set_language);
+
defsubr (&Sns_read_file_name);
defsubr (&Sns_get_resource);
defsubr (&Sns_set_resource);
diff --git a/src/nsterm.h b/src/nsterm.h
index 4b246bd..c0d15a7 100644
--- a/src/nsterm.h
+++ b/src/nsterm.h
@@ -757,6 +757,8 @@ extern EmacsMenu *mainMenu, *svcsMenu, *dockMenu;
#define KEY_NS_NEW_FRAME ((1<<28)|(0<<16)|12)
#define KEY_NS_TOGGLE_TOOLBAR ((1<<28)|(0<<16)|13)
#define KEY_NS_SHOW_PREFS ((1<<28)|(0<<16)|14)
+#define KEY_NS_CHECK_SPELLING ((1<<28)|(0<<16)|20)
+#define KEY_NS_SPELLING_CHANGE ((1<<28)|(0<<16)|21)
/* could use list to store these, but rest of emacs has a big infrastructure
for managing a table of bitmap "records" */
diff --git a/src/nsterm.m b/src/nsterm.m
index 4d9d105..4e41856 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -5746,6 +5746,54 @@ not_in_argv (NSString *arg)
}
+/* called on spelling text change (part of NSChangeSpelling protocol) */
+- (void)changeSpelling: (id)sender
+{
+ NSEvent *e =[[self window] currentEvent];
+
+ NSTRACE (changeSpelling);
+ if (!emacs_event)
+ return;
+
+ /* SET_FRAME_GARBAGED (emacsframe); needed? */
+
+ emacs_event->kind = NS_NONKEY_EVENT;
+ emacs_event->modifiers = 0;
+ emacs_event->code = KEY_NS_SPELLING_CHANGE;
+ ns_spelling_text = build_string ([[[(NSControl*)sender selectedCell] stringValue] UTF8String]);
+ EV_TRAILER (e);
+}
+
+- (void)ignoreSpelling:(id)sender {
+ NSInteger tag = sxhash (Fcurrent_buffer (), 0);
+
+ [[NSSpellChecker sharedSpellChecker] ignoreWord:[[sender selectedCell] stringValue]
+ inSpellDocumentWithTag: tag];
+
+ /* Note (To Do): To make the ignored words feature useful, the
+ application must store a document’s ignored words list with the
+ document. See the NSSpellChecker class description for more
+ information. [From Apple's Cocoa documentation]*/
+}
+
+
+/* Find Next button */
+- (void)checkSpelling:(id)sender {
+ NSEvent *e =[[self window] currentEvent];
+
+ NSTRACE (checkSpelling);
+ if (!emacs_event)
+ return;
+
+ SET_FRAME_GARBAGED (emacsframe);
+
+ emacs_event->kind = NS_NONKEY_EVENT;
+ emacs_event->modifiers = 0;
+ emacs_event->code = KEY_NS_CHECK_SPELLING;
+ EV_TRAILER (e);
+}
+
+
- (BOOL)acceptsFirstResponder
{
NSTRACE ("[EmacsView acceptsFirstResponder]");
@@ -8652,6 +8700,10 @@ syms_of_nsterm (void)
"String for visualizing working composition sequence.");
ns_working_text =Qnil;
+ DEFVAR_LISP ("ns-spelling-text", ns_spelling_text,
+ "The substitute text corresponding to the ns-spelling-change event.");
+ ns_spelling_text =Qnil;
+
DEFVAR_LISP ("ns-input-font", ns_input_font,
"The font specified in the last NS event.");
ns_input_font =Qnil;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment