4444(require 'thingatpt )
4545(require 'dom )
4646
47+ (when (version< emacs-version " 29.1" )
48+ (eval-and-compile
49+ (defmacro with-undo-amalgamate (&rest body )
50+ " Like `progn' but perform BODY with amalgamated undo barriers.
51+
52+ This allows multiple operations to be undone in a single step.
53+ When undo is disabled this behaves like `progn' ."
54+ (declare (indent 0 ) (debug t ))
55+ (let ((handle (make-symbol " --change-group-handle--" )))
56+ `(let ((,handle (prepare-change-group ))
57+ ; ; Don't truncate any undo data in the middle of this,
58+ ; ; otherwise Emacs might truncate part of the resulting
59+ ; ; undo step: we want to mimic the behavior we'd get if the
60+ ; ; undo-boundaries were never added in the first place.
61+ (undo-outer-limit nil )
62+ (undo-limit most-positive-fixnum)
63+ (undo-strong-limit most-positive-fixnum))
64+ (unwind-protect
65+ (progn
66+ (activate-change-group ,handle )
67+ ,@body )
68+ (progn
69+ (accept-change-group ,handle )
70+ (undo-amalgamate-change-group ,handle ))))))))
71+
4772(defgroup speed-type nil
4873 " Practice touch-typing in Emacs."
4974 :group 'games )
@@ -320,7 +345,6 @@ If nil, the completion is only triggered if all characters are typed."
320345 " Face for point-movement in preview buffer." )
321346
322347; ; internal variables
323-
324348(defvar speed-type--gb-url-format " https://www.gutenberg.org/cache/epub/%d/pg%d.txt" )
325349
326350(defvar speed-type-explaining-message "
@@ -397,7 +421,6 @@ It's the point within speed-type-buffer.")
397421 " Used to calculate duration of a speed-type session." )
398422(defvar-local speed-type--last-modified-tick nil
399423 " Used to determine if there was only a property change between before- and after-functions." )
400-
401424(defvar-local speed-type--last-changed-text nil
402425 " Used to store characters which are going be compared against.
403426
@@ -1083,7 +1106,6 @@ Whitespace is determined using `char-syntax'."
10831106 (setq i (1+ i)))
10841107 still-correct))
10851108
1086-
10871109(defun speed-type--check-same (pos a b )
10881110 " Return non-nil if A[POS] and B[POS] are identical or both whitespace.
10891111
@@ -1304,7 +1326,6 @@ value return nil."
13041326 (setq-local speed-type--last-position new-last-pos))
13051327 (read-only-mode )))))
13061328
1307-
13081329(defvar current-correct-streak 0 " Tracks the correct streak since last error or beginning." )
13091330(defun speed-type--diff (orig new start end )
13101331 " Synchronise local buffer state with buffer-content by comparing ORIG and NEW.
@@ -1355,11 +1376,18 @@ END is a point where the check stops to scan for diff."
13551376 (message " Wrong key " ))
13561377 (not any-error)))
13571378
1358- (defun speed-type--update-overlay (start end )
1359- " Undo does not track overlay changes but it does update text-properties.
1360- Undo has finished it's job and we now update the overlay to the new
1361- text-property value."
1362- (when-let ((orig-end (cdr (get-text-property (1- (point-max )) 'speed-type-orig-pos ))))
1379+ (defun speed-type--sync-after-undo (start end )
1380+ " Sync overlay between START and END with whatever text-properties appear there.
1381+
1382+ Undo does not track overlay changes but it does update text-properties.
1383+
1384+ This function should be called after undo has finished it's job and the
1385+ region got new text-property values.
1386+
1387+ `buffer-undo-list' is per buffer which is why point is synced in
1388+ content-buffer manually if there is a add-extra-word-function."
1389+ (when-let ((add-word-fn speed-type--add-extra-word-content-fn)
1390+ (orig-end (cdr (get-text-property (1- (point-max )) 'speed-type-orig-pos ))))
13631391 (with-current-buffer speed-type--content-buffer (goto-char orig-end)))
13641392 (remove-overlays start end 'face 'speed-type-correct-face )
13651393 (remove-overlays start end 'face 'speed-type-error-face )
@@ -1384,12 +1412,12 @@ text-property value."
13841412LENGTH is ignored. Used for hook AFTER-CHANGE-FUNCTIONS.
13851413Make sure that the contents don't actually change, but rather the contents
13861414are color coded and stats are gathered about the typing performance."
1387- (cond (undo-in-progress (speed-type--update-overlay start end))
1415+ (cond (undo-in-progress (speed-type--sync-after-undo start end))
13881416 ((or (member this-command '(fill-paragraph ))
13891417 (= speed-type--last-modified-tick (buffer-chars-modified-tick )))
13901418 nil )
13911419 (t (progn
1392- (unless speed-type--idle-pause-timer (speed-type-- resume) )
1420+ (speed-type--resume)
13931421 (let ((new-text (buffer-substring start end))
13941422 (old-text speed-type--last-changed-text))
13951423 (speed-type--handle-del start end)
@@ -1542,30 +1570,20 @@ CALLBACK is called when the setup process has been completed."
15421570 (when (null (boundp 'speed-type--extra-word-quote ))
15431571 (setq-local speed-type--extra-word-quote nil )))
15441572 (when speed-type-provide-preview-option (speed-type--connect-preview-buffer buf content-buffer))
1545- (let ((b-inhibit-read-only inhibit-read-only)
1546- (b-buffer-undo-list buffer-undo-list)
1547- (b-inhibit-modification-hooks inhibit-modification-hooks)
1548- (b-inhibit-field-text-motion inhibit-field-text-motion))
1549- (unwind-protect
1550- (progn
1551- (setq-local inhibit-read-only t
1552- buffer-undo-list t
1553- inhibit-modification-hooks t
1554- inhibit-field-text-motion t )
1555- (insert (if (speed-type--code-buffer-p speed-type--content-buffer) (speed-type--trim text) (string-trim text)))
1556- (speed-type--replace-map-adjust-properties speed-type-replace-strings 'speed-type-orig-pos )
1557- (when speed-type-downcase (downcase-region (point-min ) (point-max )))
1558- (unless (speed-type--code-buffer-p speed-type--content-buffer)
1559- (fill-region (point-min ) (point-max ) 'none t ))
1560- (when speed-type-ignore-whitespace-for-complete
1561- (save-excursion
1562- (goto-char (point-min ))
1563- (while (search-forward-regexp " [[:blank:]\n ]+" nil t 1 )
1564- (add-text-properties (match-beginning 0 ) (match-end 0 ) '(speed-type-char-status ignore))))))
1565- (setq-local inhibit-read-only b-inhibit-read-only
1566- buffer-undo-list b-buffer-undo-list
1567- inhibit-modification-hooks b-inhibit-modification-hooks
1568- inhibit-field-text-motion b-inhibit-field-text-motion)))
1573+ (let ((inhibit-read-only t )
1574+ (buffer-undo-list t )
1575+ (inhibit-modification-hooks t )
1576+ (inhibit-field-text-motion t ))
1577+ (insert (if (speed-type--code-buffer-p speed-type--content-buffer) (speed-type--trim text) (string-trim text)))
1578+ (speed-type--replace-map-adjust-properties speed-type-replace-strings 'speed-type-orig-pos )
1579+ (when speed-type-downcase (downcase-region (point-min ) (point-max )))
1580+ (unless (speed-type--code-buffer-p speed-type--content-buffer)
1581+ (fill-region (point-min ) (point-max ) 'none t ))
1582+ (when speed-type-ignore-whitespace-for-complete
1583+ (save-excursion
1584+ (goto-char (point-min ))
1585+ (while (search-forward-regexp " [[:blank:]\n ]+" nil t 1 )
1586+ (add-text-properties (match-beginning 0 ) (match-end 0 ) '(speed-type-char-status ignore))))))
15691587 (set-buffer-modified-p nil )
15701588 (switch-to-buffer buf)
15711589 (when (eq speed-type-provide-preview-option t )
@@ -1661,7 +1679,7 @@ If IGNORE is non-nil, will ignore whitespace which will not account to
16611679the approximated length.
16621680
16631681If START and END are the same and TOLERANCE is zero will just return
1664- ' (START END).
1682+ \ ( START END).
16651683
16661684Use `save-excursion' to prevent point-movement."
16671685 (dolist (arg `((START . ,start ) (END . ,end ) (MIN . ,min ) (MAX . ,max ) (TOLERANCE . ,tolerance )))
0 commit comments