summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2024-04-29 19:05:37 -0500
committerCraig Jennings <c@cjennings.net>2024-04-29 19:05:37 -0500
commit9b92e9b66a821abf0124d7766b05f9a8bdf685b6 (patch)
tree96cb49736104d2228069288ef10a17b1eaae5b1a
parente9e5696bc95d34aad8eb561d7458ac21b06359b0 (diff)
downloaddotemacs-9b92e9b66a821abf0124d7766b05f9a8bdf685b6.tar.gz
dotemacs-9b92e9b66a821abf0124d7766b05f9a8bdf685b6.zip
refactored flyspell-and-abbrev
- refactored flyspell-and-abbrev, adding ert tests - remove unsetting the C-; keybinding for personal-keymap from Flyspell to custom-functions with the rest of the keybinding commands.
-rw-r--r--assets/test_misspellings.org5
-rw-r--r--modules/custom-functions.el5
-rw-r--r--modules/flyspell-config.el273
-rw-r--r--tests/test-flyspell-config-functions.el149
4 files changed, 302 insertions, 130 deletions
diff --git a/assets/test_misspellings.org b/assets/test_misspellings.org
new file mode 100644
index 00000000..7f492757
--- /dev/null
+++ b/assets/test_misspellings.org
@@ -0,0 +1,5 @@
+
+
+You would think that, being a librarian would be an easy and relaxing job - belying about in a quiet room, inhaling the fragrance of old pages, but it's actualy quite chalanging. For one, there's a lot of people that don't know how to mantain silence, and you have to constanly remind them to respect others. The cataloguing proces isn't as simpe as you might imagine either. You have to clasify each book according to it's author, do the Dewey Decimal System, and contole the circulation of the books.
+
+Running a libarary isn't only about keeping books and lending them out, it's a buisness that needs efective managment. It's unfortunete that it's often underestimated, but running a libaray can be as tough as any other job, requirering various skills and pasience. And let's not talk about late returners, they can be the bigest headache of all. Making sure books are returned on time and in good shape is a constant struggle. Always remember, libraries are truly treasures of knowledge, and the librarians are the guardians preserving them.
diff --git a/modules/custom-functions.el b/modules/custom-functions.el
index a36dfe80..68c2270f 100644
--- a/modules/custom-functions.el
+++ b/modules/custom-functions.el
@@ -542,7 +542,12 @@ Uses `sortable-time-format' for the formatting the date/time."
;; ------------------------------ Personal Keymap ------------------------------
;; a keymap to use the above functions. prefix key: "C-;"
+
+;; Unset Flyspell's greedy keybinding C-; so it can be used for personal-keymap
(global-unset-key (kbd "C-;"))
+(eval-after-load "flyspell"
+ '(define-key flyspell-mode-map (kbd "C-;") nil))
+
(defvar personal-keymap
(let ((map (make-sparse-keymap)))
;; un/arrayify
diff --git a/modules/flyspell-config.el b/modules/flyspell-config.el
index ad0945c4..1e3f0535 100644
--- a/modules/flyspell-config.el
+++ b/modules/flyspell-config.el
@@ -3,182 +3,195 @@
;;; Commentary:
;; Spell-Checking: Flyspell
-;; C-' is now my main interface for all spell checking. It checks one word, then
-;; you may proceed to your next misspelling by selecting 'C-' again.
-;; Flyspell will automatically run in a mode that's appropriate for the buffer type
+;; C-' is now my main interface for all spell checking.
+
+;; 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.
+
+;; 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.
-;; If you want flyspell on in another mode (say fundamental mode), or you'd like it off
-;; to keep it from distracting, you can toggle flyspell's state with 'C-c f'
+;; 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'
+
+;; 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.
+
+;; Original idea here:
+;; 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.
-;; 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.
;;
-;; Use M-o to get to 'other options', including saving to your personal dictionary.
+;; Use M-o to get to 'other options', like saving to your personal dictionary.
-;;; Code:
+;; Note that 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:
-;; ------------------------ Reclaim Flyspell Keybinding ------------------------
-;; remove one of flyspell's many greedy keyboard bindings
-;; this is now used for my personal-keymap
+;; (eval-after-load "flyspell"
+;; '(define-key flyspell-mode-map (kbd "C-;") nil))
-(eval-after-load "flyspell"
- '(define-key flyspell-mode-map (kbd "C-;") nil))
+;;; Code:
-;; --------------------------------- Flyspell --------------------------------
-;; there's some ispell settings here as well.
+;; ---------------------------- Ispell And Flyspell ----------------------------
-(use-package flyspell
+
+(use-package ispell
:defer .5
:ensure nil ;; built-in
:config
- (setq ispell-alternate-dictionary (concat user-emacs-directory "assets/english-words.txt"))
+ (setq ispell-alternate-dictionary
+ (concat user-emacs-directory "assets/english-words.txt"))
(setq ispell-dictionary "american") ; better for aspell
+ ;; use aspell rather than ispell
+ (setq ispell-program-name "aspell")
+ ;; in aspell "-l" means --list, not --lang
+ (setq ispell-list-command "--list")
(setq ispell-extra-args '("--sug-mode=ultra" "-W" "3" "--lang=en_US"))
- (setq ispell-list-command "--list") ;; in aspell "-l" means --list, not --lang
(setq ispell-local-dictionary "en_US")
- (setq ispell-program-name "aspell") ;; use aspell instead of ispell
- (setq ispell-local-dictionary-alist '(("en_US" "[[:alpha:]]" "[^[:alpha:]]" "['‘’]"
- t ; Many other characters
- ("-d" "en_US") nil utf-8)))
-
+ (setq ispell-local-dictionary-alist
+ '(("en_US" "[[:alpha:]]" "[^[:alpha:]]" "['‘’]"
+ t ;; Many other characters
+ ("-d" "en_US") nil utf-8)))
+ ;; personal directory goes with sync'd files
(setq ispell-personal-dictionary
- (concat sync-dir "aspell-personal-dictionary")) ;; personal directory goes with sync'd files
- (setq flyspell-issue-message-flag nil) ;; don't print message for every word when checking
- (add-to-list 'ispell-skip-region-alist '("^#+BEGIN_SRC" . "^#+END_SRC"))) ;; skip code blocks in org mode
+ (concat sync-dir "aspell-personal-dictionary"))
+ ;; skip code blocks in org mode
+ (add-to-list 'ispell-skip-region-alist '("^#+BEGIN_SRC" . "^#+END_SRC")))
+(use-package flyspell
+ :defer .5
+ :after ispell
+ :ensure nil ;; built-in
+ :config
+ ;; don't print message for every word when checking
+ (setq flyspell-issue-message-flag nil))
+
(use-package flyspell-correct
- :defer 1
:after flyspell
- :bind
- (:map flyspell-mode-map
- ("C-'" . cj/flyspell-then-abbrev))) ;; disallow other entries to spelling corrections
-
+ :defer .5)
(use-package flyspell-correct-ivy
- :defer 1
- :after (ivy flyspell-correct)
- :init
- (setq flyspell-correct-interface #'flyspell-correct-ivy)) ;; use ivy as the flyspell interface
+ :after flyspell-correct
+ :defer .5)
+;; ------------------------------ 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 )
;; ------------------------ Flyspell On For Buffer Type ------------------------
-;; check strings and comments in prog mode; everything in text mode
+;; check strings and comments in prog mode; check everything in text mode
(defun flyspell-on-for-buffer-type ()
- "Enable Flyspell appropriately for the major mode and check the current buffer.
+ "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. Otherwise, flyspell is turned off."
+all text."
(interactive)
- (if (not (symbol-value flyspell-mode)) ; if not already on
- (progn
- (if (derived-mode-p 'prog-mode)
- (progn
- (flyspell-prog-mode)
- (flyspell-buffer)))
- (if (derived-mode-p 'text-mode)
- (progn
- (flyspell-mode 1)
- (flyspell-buffer))
- ;; else
- (progn
- (flyspell-mode 0))))))
+ (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)
;; ---------------------------- Flyspell Then Abbrev ---------------------------
-;; A stroke of genius from Artur Malabarba.
-;; http://endlessparentheses.com/ispell-and-abbrev-the-perfect-auto-correct.html
-;; Spell checks backwards (after you've noticed a typo). Corrects, then automatically
-;; creates an abbrev to autocorrect the same misspelling.
-;; Bound to C-; above.
+;; Spell check the buffer and create abbrevs to avoid future misspellings.
+(require 'files)
(setq save-abbrevs 'silently)
(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)
+ (lambda (a b)
+ (> (overlay-start a) (overlay-start b))))))
+ ;; search for previous flyspell overlay
+ (while (and overlay-list
+ (or (not (flyspell-overlay-p (car overlay-list)))
+ ;; 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
+ (when overlay-list
+ ;; otherwise, return the overlay start position
+ (overlay-start (car overlay-list)))))
+
+
+(defun cj/flyspell-goto-previous-misspelling (position)
+ "Go to the first misspelled word before the given POSITION.
+Return the misspelled word if found or nil if not. Leave the point at the
+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")
+ (add-hook 'pre-command-hook
+ (function flyspell-auto-correct-previous-hook) t t)
+ (let* ((overlay-position (cj/find-previous-flyspell-overlay position))
+ (misspelled-word (when overlay-position
+ (goto-char overlay-position)
+ (thing-at-point 'word))))
+ (if misspelled-word
+ (downcase misspelled-word)
+ nil)))
+
(defun cj/flyspell-then-abbrev (p)
- "Call `ispell-word', then create an abbrev for it.
-With prefix P, create local abbrev. Otherwise it will
-be global."
+ "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."
(interactive "P")
+ (flyspell-buffer)
(save-excursion
- (if (flyspell-goto-previous-word (point))
- (let ((bef (downcase (or (thing-at-point 'word)
- "")))
- aft)
- (call-interactively 'flyspell-correct-at-point)
- (setq aft (downcase
- (or (thing-at-point 'word) "")))
- (unless (or (string= aft bef)
- (string= aft "")
- (string= bef ""))
- (message "\"%s\" now expands to \"%s\" %sally"
- bef aft (if p "loc" "glob"))
- (define-abbrev
- (if p local-abbrev-table global-abbrev-table)
- bef aft)))
- (message "Cannot find a misspelled word"))))
-
-(defun flyspell-goto-previous-word (position)
- "Go to the first misspelled word that occurs before point.
-But don't look beyond what's visible on the screen."
- (interactive "d")
- (let ((top (window-start))
- (bot (window-end)))
- (save-restriction
- (narrow-to-region top bot)
- (overlay-recenter (point))
- (add-hook 'pre-command-hook
- (function flyspell-auto-correct-previous-hook) t t)
- (unless flyspell-auto-correct-previous-pos
- ;; only reset if a new overlay exists
- (setq flyspell-auto-correct-previous-pos nil)
- (let ((overlay-list (overlays-in (point-min) position))
- (new-overlay 'dummy-value))
- ;; search for previous (new) flyspell overlay
- (while (and new-overlay
- (or (not (flyspell-overlay-p new-overlay))
- ;; check if its face has changed
- (not (eq (get-char-property
- (overlay-start new-overlay) 'face)
- 'flyspell-incorrect))))
- (setq new-overlay (car-safe overlay-list))
- (setq overlay-list (cdr-safe overlay-list)))
- ;; if nothing new exits new-overlay should be nil
- (if new-overlay ;; the length of the word may change so go to the start
- (setq flyspell-auto-correct-previous-pos
- (overlay-start new-overlay)))))
- (if (not flyspell-auto-correct-previous-pos)
- nil
- (goto-char flyspell-auto-correct-previous-pos)
- t))))
-
-;; ------------------------------ Flyspell Toggle ------------------------------
-;; easy toggling flyspell which also leverages 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"))))
-(global-set-key (kbd "C-c f") 'flyspell-toggle )
-
+ (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)
(provide 'flyspell-config)
;;; flyspell-config.el ends here.
diff --git a/tests/test-flyspell-config-functions.el b/tests/test-flyspell-config-functions.el
new file mode 100644
index 00000000..43d62dba
--- /dev/null
+++ b/tests/test-flyspell-config-functions.el
@@ -0,0 +1,149 @@
+;;; test-flyspell-config-functions.el --- -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Evaluate the buffer, then run (ert-all-tests).
+
+;;; Code:
+
+(add-to-list 'load-path (concat user-emacs-directory "modules"))
+(require 'flyspell-config)
+
+;; --------------------------- Flyspell Overlay Tests --------------------------
+
+(ert-deftest cj/flyspell-overlay-test-positive ()
+ "Simplest positive test for \='cj/find-previous-flyspell-overlay\='.
+With one misspelling, cj/find-previous-flyspell-overlay should return the
+character position at the beginning of the misspelled word."
+ (with-temp-buffer
+ (let ((misspelled "mispeled")
+ (overlay-pos))
+ ;; insert some text
+ (insert (format "some text for testing. %s" misspelled))
+
+ ;; trigger flyspell and wait for it to complete
+ (flyspell-buffer)
+ (sit-for 1)
+
+ ;; call the function with position at end of the buffer
+ (setq overlay-pos (cj/find-previous-flyspell-overlay (point-max)))
+
+ ;; test flyspell-auto-correct-previous-pos is at char position of 'mispeled'.
+ (should (eq (- (point-max) (length misspelled)) overlay-pos)))))
+
+(ert-deftest cj/flyspell-overlay-test-negative ()
+ "Simplest negative test for \='cj/find-previous-flyspell-overlay\='.
+With no misspelled words, cj/find-previous-flyspell-overlay should return nil."
+ (with-temp-buffer
+ (insert "This is a correctly spelled sentence.")
+ (flyspell-buffer)
+ ;; No overlay should exist, so test the result is nil.
+ (should-not (cj/find-previous-flyspell-overlay (point-max)))))
+
+(ert-deftest cj/flyspell-overlay-test-positive-multiple ()
+ "Positive test for \='cj/find-previous-flyspell-overlay\='.
+With several misspellings above and below, cj/find-previous-flyspell-overlay
+should return the character position at the beginning of the previous misspelled
+word."
+ (with-temp-buffer
+ (let ((misspelled0 "incorect")
+ (misspelled1 "wrongg")
+ (misspelled2 "erroor")
+ (misspelled3 "mistken")
+ (actual-pos)
+ (expected-pos)
+ (between-pos))
+
+ ;; insert some text with misspellings
+ (insert (format "flyspell should catch this: %s" misspelled0))
+ (insert (format "flyspell should catch this: %s" misspelled1))
+
+ ;; calculate the overlay's expected position based on our current position
+ (setq expected-pos (- (point) (length misspelled1)))
+
+ ;; calculate a position in between misspellings
+ (setq between-pos (+ expected-pos (length misspelled1) 5))
+
+ ;; insert the rest of the misspellings
+ (insert (format "flyspell should catch this: %s" misspelled2))
+ (insert (format "flyspell should catch this: %s" misspelled3))
+
+ ;; trigger Flyspell and wait for it to identify all misspellings.
+ (flyspell-buffer)
+ (sit-for 1)
+
+ ;; call the function with position in between misspellings
+ (setq actual-pos (cj/find-previous-flyspell-overlay between-pos))
+ (should (eq expected-pos actual-pos)))))
+
+
+(ert-deftest cj/flyspell-goto-previous-misspelling-positive ()
+ "Positive test for \='cj/flyspell-goto-previous-misspelling\='.
+With a simple misspelling above, cj/flyspell-goto-previous-misspelling
+should land on the next misspelled word."
+ (with-temp-buffer
+ (let ((misspelled-word "incorect")
+ (actual-word))
+
+ ;; insert some text with misspellings
+ (insert (format "flyspell should catch this: %s" misspelled-word))
+
+ ;; trigger Flyspell and wait for it to identify all misspellings.
+ (flyspell-buffer)
+ (sit-for 1)
+
+ ;; call the function with position in between misspellings
+ (setq actual-word (cj/flyspell-goto-previous-misspelling (point-max)))
+ (should (string= misspelled-word actual-word)))))
+
+(ert-deftest cj/flyspell-goto-previous-misspelling-negative ()
+ "Negative test for \='cj/flyspell-goto-previous-misspelling\='.
+With no misspellings, cj/flyspell-goto-previous-misspelling return nil."
+ (with-temp-buffer
+ (let ((expected nil)
+ (result))
+
+ ;; insert some text with misspellings
+ (insert (format "None of these words are misspelled."))
+
+ ;; trigger Flyspell and wait for it to identify all misspellings.
+ (flyspell-buffer)
+ (sit-for 1)
+
+ ;; call the function with position in between misspellings
+ (setq result (cj/flyspell-goto-previous-misspelling (point-max)))
+ (message "result is %s" result)
+ (should (eq result expected)))))
+
+(ert-deftest cj/flyspell-goto-previous-misspelling-positive-multiple ()
+ "Positive test for \='cj/flyspell-goto-previous-misspelling\='.
+With several misspellings above and below, cj/flyspell-goto-previous-misspelling
+should return the misspelled word just previous to the position of the cursor."
+ (with-temp-buffer
+ (let ((misspelled0 "incorect")
+ (misspelled1 "wrongg")
+ (misspelled2 "erroor")
+ (misspelled3 "mistken")
+ (result)
+ (between-pos))
+
+ ;; insert some text with misspellings
+ (insert (format "flyspell should catch this: %s\n" misspelled0))
+ (insert (format "flyspell should catch this: %s\n" misspelled1))
+
+ ;; calculate a position in between misspellings
+ (setq between-pos (+ (point) (length misspelled1) 5))
+
+ ;; insert the rest of the misspellings
+ (insert (format "flyspell should catch this: %s\n" misspelled2))
+ (insert (format "flyspell should catch this: %s\n" misspelled3))
+
+ ;; trigger Flyspell and wait for it to identify all misspellings.
+ (flyspell-buffer)
+ (sit-for 1)
+
+ ;; call the function with position in between misspellings
+ (setq result (cj/flyspell-goto-previous-misspelling between-pos))
+ (should (string= result misspelled1)))))
+
+(provide 'test-flyspell-config-functions)
+;;; test-flyspell-config-functions.el ends here.