summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/flyspell-and-abbrev.el250
1 files changed, 141 insertions, 109 deletions
diff --git a/modules/flyspell-and-abbrev.el b/modules/flyspell-and-abbrev.el
index 978f8208..12e0d348 100644
--- a/modules/flyspell-and-abbrev.el
+++ b/modules/flyspell-and-abbrev.el
@@ -4,65 +4,76 @@
;;; Commentary:
;; WORKFLOW:
-;; C-' is now my main interface for all spell checking.
+;; This module provides intelligent spell checking with automatic abbreviation
+;; creation to prevent repeated misspellings.
;;
-;; The workflow is that it finds the nearest misspelled word above where the
-;; cursor is, allows for saving or correcting, then stops. You may proceed to
-;; the next misspelling by selecting C-' again.
+;; KEYBINDINGS:
+;; C-' - Main spell check interface (cj/flyspell-then-abbrev)
+;; C-c f - Toggle flyspell on/off (cj/flyspell-toggle)
+;; M-o - Access 'other options' during correction (save to dictionary, etc.)
;;
-;; Use M-o to get to 'other options', like saving to your personal dictionary.
+;; SPELL CHECKING WORKFLOW:
+;; 1. Press C-' to start spell checking
+;; 2. Finds the nearest misspelled word above the cursor
+;; 3. Prompts for correction or allows saving to personal dictionary
+;; 4. Press C-' again to move to the next misspelling
+;; 5. Each correction automatically creates an abbrev for future auto-expansion
;;
-;; Flyspell will automatically run in a mode appropriate for the buffer type
-;; - if it's a programming mode, it will only check comments
-;; - if in text mode, it will check everything
-;; - otherwise it will turn off.
-;; This check happens on every mode switch.
+;; FLYSPELL ACTIVATION:
+;; Flyspell is NOT automatically enabled. You activate it manually:
+;; - C-c f - Toggle flyspell on (uses smart mode detection) or off
+;; - C-' - Runs flyspell-buffer then starts correction workflow
;;
-;; If you want flyspell on in another mode (say fundamental mode), or you want
-;; to turn it off, you can toggle flyspell's state with 'C-c f'
+;; When enabled, flyspell adapts to the buffer type:
+;; - Programming modes (prog-mode): Only checks comments and strings
+;; - Text modes (text-mode): Checks all text
+;; - Other modes: Must enable manually with C-c f
;;
-;; The nicest thing is that each spell correction creates an abbrev. This
-;; essentially is a shortcut that expands that same misspelling to the correct
-;; spelling the next time it's typed. That idea comes courtesy Artur Malabarba,
-;; and it's increased my overall typing speed.
+;; ABBREVIATION AUTO-EXPANSION:
+;; Each spell correction creates an abbrev that auto-expands the misspelling
+;; to the correct spelling when you type it in the future. This significantly
+;; increases typing speed over time.
;;
-;; Original idea here:
+;; Original idea from Artur Malabarba:
;; http://endlessparentheses.com/ispell-and-abbrev-the-perfect-auto-correct.html
;;
-;; The code below is my refactoring of Artur Malabarba's code, and using
-;; flyspell rather than ispell.
-;;
;; NOTES:
-;;
-;; FYI, the keybinding typically taken for the flyspell-mode-map "C-;" has
-;; been deliberately hijacked in custom-functions.el for my personal-keymap.
-;; This is the code run there:
-
-;; (eval-after-load "flyspell"
-;; '(define-key flyspell-mode-map (kbd "C-;") nil))
+;; The default flyspell keybinding "C-;" is unbound in this config as it's
+;; used for the custom keymap (cj/custom-keymap).
;;; Code:
+;; Forward declarations
+(eval-when-compile (defvar org-dir))
+(defvar flyspell-mode)
+(defvar ispell-list-command)
+(defvar ispell-skip-region-alist)
+(declare-function flyspell-overlay-p "flyspell")
+(declare-function flyspell-auto-correct-previous-hook "flyspell")
+(declare-function flyspell-correct-at-point "flyspell-correct")
+(declare-function flyspell-buffer "flyspell")
+(declare-function flyspell-prog-mode "flyspell")
+(declare-function thing-at-point "thingatpt")
+
;; ----------------------------------- Abbrev ----------------------------------
-(use-package abbrev-mode
- :ensure nil
- :defer 0.5
- :custom
- (abbrev-file-name (concat user-emacs-directory "assets/abbrev_defs"))
- :config
- (abbrev-mode 1))
+;; Defer abbrev configuration until first use
+(with-eval-after-load 'abbrev
+ (setq abbrev-file-name (concat user-emacs-directory "assets/abbrev_defs"))
+ (setq save-abbrevs 'silently))
+
+;; Enable abbrev-mode by default (lightweight, okay to do eagerly)
+(setq-default abbrev-mode t)
;; ---------------------------- Ispell And Flyspell ----------------------------
(use-package ispell
- :defer .5
:ensure nil ;; built-in
+ :commands (ispell-word ispell-region ispell-buffer)
:config
- ;; (setopt ispell-alternate-dictionary
- ;; (concat user-emacs-directory "assets/english-words.txt"))
- (setopt text-mode-ispell-word-completion nil)
- (setopt ispell-alternate-dictionary nil)
+ ;; Use setq consistently (setopt requires Emacs 29+)
+ (setq text-mode-ispell-word-completion nil)
+ (setq ispell-alternate-dictionary nil)
(setq ispell-dictionary "american") ; better for aspell
;; use aspell rather than ispell
@@ -76,7 +87,7 @@
(setq ispell-extra-args '("--sug-mode=ultra" "-W" "3" "--lang=en_US"))
(setq ispell-local-dictionary "en_US")
(setq ispell-local-dictionary-alist
- '(("en_US" "[[:alpha:]]" "[^[:alpha:]]" "['‘’]"
+ '(("en_US" "[[:alpha:]]" "[^[:alpha:]]" "[''']"
t ;; Many other characters
("-d" "en_US") nil utf-8)))
;; personal directory goes with sync'd files
@@ -86,8 +97,8 @@
(add-to-list 'ispell-skip-region-alist '("^#+BEGIN_SRC" . "^#+END_SRC")))
(use-package flyspell
- :after (ispell abbrev)
:ensure nil ;; built-in
+ :commands (flyspell-mode flyspell-prog-mode flyspell-buffer)
:config
;; unset keybinding as we're using it for cj/custom keymap
(keymap-unset flyspell-mode-map "C-;")
@@ -95,73 +106,77 @@
(setq flyspell-issue-message-flag nil))
(use-package flyspell-correct
- :after flyspell
- :defer .5)
+ :commands flyspell-correct-at-point)
;; ------------------------------ Flyspell Toggle ------------------------------
;; easy toggling flyspell and also leverage the 'for-buffer-type' functionality.
-;; (defun flyspell-toggle ()
-;; "Turn Flyspell on if it is off, or off if it is on.
-
-;; When turning on,it uses `flyspell-on-for-buffer-type' so code-vs-text is
-;; handled appropriately."
-;; (interactive)
-;; (if (symbol-value flyspell-mode)
-;; (progn ; flyspell is on, turn it off
-;; (message "Flyspell off")
-;; (flyspell-mode -1))
-;; ;; else - flyspell is off, turn it on
-;; (progn
-;; (flyspell-on-for-buffer-type)
-;; (message "Flyspell on"))))
-;; (define-key global-map (kbd "C-c f") 'flyspell-toggle )
+;;;###autoload
+(defun cj/flyspell-toggle ()
+ "Turn Flyspell on if it is off, or off if it is on.
+
+When turning on, it uses `cj/flyspell-on-for-buffer-type' so code-vs-text is
+handled appropriately."
+ (interactive)
+ ;; Check if spell checker is available
+ (unless (or (executable-find "aspell")
+ (executable-find "ispell")
+ (executable-find "hunspell"))
+ (user-error "No spell checker found. Install aspell, ispell, or hunspell"))
+
+ (if (bound-and-true-p flyspell-mode)
+ (progn ; flyspell is on, turn it off
+ (flyspell-mode -1)
+ (message "Flyspell off"))
+ ;; else - flyspell is off, turn it on
+ (cj/flyspell-on-for-buffer-type)
+ (message "Flyspell on")))
;; ------------------------ Flyspell On For Buffer Type ------------------------
;; check strings and comments in prog mode; check everything in text mode
-;; (defun flyspell-on-for-buffer-type ()
-;; "Enable Flyspell for the major mode and check the current buffer.
-
-;; If flyspell is already enabled, do nothing. If the mode is derived from
-;; `prog-mode', enable `flyspell-prog-mode' so only strings and comments get
-;; checked. If the buffer is text based `flyspell-mode' is enabled to check
-;; all text."
-;; (interactive)
-;; (unless flyspell-mode ; if not already on
-;; (cond
-;; ((derived-mode-p 'prog-mode)
-;; (flyspell-prog-mode)
-;; (flyspell-buffer)
-;; ((derived-mode-p 'text-mode)
-;; (flyspell-mode 1)
-;; (flyspell-buffer))))))
-
-;; (add-hook 'after-change-major-mode-hook 'flyspell-on-for-buffer-type)
-;; (add-hook 'find-file-hook 'flyspell-on-for-buffer-type)
+(defun cj/flyspell-on-for-buffer-type ()
+ "Enable Flyspell for the major mode and check the current buffer.
+
+If flyspell is already enabled, do nothing. If the mode is derived from
+`prog-mode', enable `flyspell-prog-mode' so only strings and comments get
+checked. If the buffer is text based `flyspell-mode' is enabled to check
+all text."
+ (interactive)
+ (unless (bound-and-true-p flyspell-mode) ; if not already on
+ (cond
+ ((derived-mode-p 'prog-mode)
+ (flyspell-prog-mode)
+ (flyspell-buffer))
+ ((derived-mode-p 'text-mode)
+ (flyspell-mode 1)
+ (flyspell-buffer)))))
+
+;; Note: NOT auto-enabling on hooks - user activates manually with C-' or C-c f
+;; (add-hook 'after-change-major-mode-hook 'cj/flyspell-on-for-buffer-type)
+;; (add-hook 'find-file-hook 'cj/flyspell-on-for-buffer-type)
;; ---------------------------- Flyspell Then Abbrev ---------------------------
;; Spell check the buffer and create abbrevs to avoid future misspellings.
-(setq-default abbrev-mode t)
-
(defun cj/find-previous-flyspell-overlay (position)
"Locate the Flyspell overlay immediately previous to a given POSITION."
- ;; sort the overlays into position order
- (let ((overlay-list (sort (overlays-in (point-min) position)
+ (require 'cl-lib)
+ ;; Sort overlays into position order (non-destructive copy)
+ (let ((overlay-list (sort (cl-copy-list (overlays-in (point-min) position))
(lambda (a b)
(> (overlay-start a) (overlay-start b))))))
- ;; search for previous flyspell overlay
+ ;; Search for previous flyspell overlay
(while (and overlay-list
(or (not (flyspell-overlay-p (car overlay-list)))
- ;; check if its face has changed
+ ;; Check if its face has changed
(not (eq (get-char-property
(overlay-start (car overlay-list)) 'face)
'flyspell-incorrect))))
(setq overlay-list (cdr overlay-list)))
- ;; if no previous overlay exists, return nil
+ ;; If no previous overlay exists, return nil
(when overlay-list
- ;; otherwise, return the overlay start position
+ ;; Otherwise, return the overlay start position
(overlay-start (car overlay-list)))))
@@ -172,6 +187,7 @@ beginning of the misspelled word. Setting the hook on pre-command ensures that
any started Flyspell corrections complete before running other commands in the
buffer."
(interactive "d")
+ (require 'thingatpt) ;; Lazy-load only when function is called
(add-hook 'pre-command-hook
(function flyspell-auto-correct-previous-hook) t t)
(let* ((overlay-position (cj/find-previous-flyspell-overlay position))
@@ -182,32 +198,48 @@ buffer."
(downcase misspelled-word)
nil)))
+;;;###autoload
(defun cj/flyspell-then-abbrev (p)
- "Call \='flyspell-correct-at-point\=' and create abbrev for future corrections.
-The abbrev is created in the local dictionary unless the prefix P
-argument is provided, when it's created in the global dictionary."
+ "Find and correct the previous misspelled word, creating an abbrev.
+
+Finds the nearest misspelled word before point, prompts for correction,
+and creates an abbrev so the misspelling auto-expands in the future.
+
+With prefix argument P, the abbrev is created in the local abbrev table.
+Without prefix argument, it's created in the global abbrev table.
+
+Press C-' repeatedly to step through misspellings one at a time."
(interactive "P")
- (unless (featurep 'files)
- (require 'files))
- (setq save-abbrevs 'silently)
- (flyspell-buffer)
- (save-excursion
- (let (misspelled-word corrected-word)
- (while (setq misspelled-word
- (cj/flyspell-goto-previous-misspelling (point)))
- (call-interactively 'flyspell-correct-at-point)
- (setq corrected-word (downcase (or (thing-at-point 'word) "")))
- (when (and misspelled-word corrected-word
- (not (string= corrected-word misspelled-word)))
- (message "\"%s\" now expands to \"%s\" %sally"
- misspelled-word corrected-word (if p "loc" "glob"))
- (define-abbrev
- (if p local-abbrev-table global-abbrev-table)
- misspelled-word corrected-word))
- (goto-char (point-min))))
- (message "Spell check complete.")))
-
-(define-key global-map (kbd "C-'") 'cj/flyspell-then-abbrev)
+ ;; Check if spell checker is available
+ (unless (or (executable-find "aspell")
+ (executable-find "ispell")
+ (executable-find "hunspell"))
+ (user-error "No spell checker found. Install aspell, ispell, or hunspell"))
+
+ ;; Run flyspell-buffer only if buffer hasn't been checked yet
+ (unless (bound-and-true-p flyspell-mode)
+ (flyspell-buffer))
+
+ (let ((misspelled-word (cj/flyspell-goto-previous-misspelling (point))))
+ (if (not misspelled-word)
+ (message "No misspellings found before point")
+ ;; Found a misspelling, correct it
+ (call-interactively 'flyspell-correct-at-point)
+ (let ((corrected-word (downcase (or (thing-at-point 'word) ""))))
+ (when (and misspelled-word corrected-word
+ (not (string= corrected-word misspelled-word)))
+ (message "\"%s\" now expands to \"%s\" %sally"
+ misspelled-word corrected-word (if p "loc" "glob"))
+ (define-abbrev
+ (if p local-abbrev-table global-abbrev-table)
+ misspelled-word corrected-word))))))
+
+;; -------------------------------- Keybindings --------------------------------
+;; Global keybindings for spell checking commands
+;; With autoload cookies, these will lazy-load the file when pressed
+
+;;;###autoload (keymap-set global-map "C-c f" #'cj/flyspell-toggle)
+;;;###autoload (keymap-set global-map "C-'" #'cj/flyspell-then-abbrev)
(provide 'flyspell-and-abbrev)
;;; flyspell-and-abbrev.el ends here.