branch: elpa/evil-numbers
commit e8dfba58c9bd109856695ac42f4994a35ae823fe
Author: Campbell Barton <[email protected]>
Commit: Campbell Barton <[email protected]>

    Cleanup: split skip characters matching out into a function
    
    Prepare for fixing #17
---
 evil-numbers.el | 179 ++++++++++++++++++++++++++++++++++++++------------------
 1 file changed, 121 insertions(+), 58 deletions(-)

diff --git a/evil-numbers.el b/evil-numbers.el
index 66acf52460..8e49c7186c 100644
--- a/evil-numbers.el
+++ b/evil-numbers.el
@@ -206,17 +206,26 @@ number with a + sign."
           (or
            ;; Find binary literals.
            (evil-numbers--search-and-replace
-            '(("0" . 1) ("bB" . 1) ("01" . nil))
+            '(("0"  . 1)
+              ("bB" . 1)
+              ("01" . +))
+            3 ;; Number group.
             amount 2)
 
            ;; Find octal literals.
            (evil-numbers--search-and-replace
-            '(("0" . 1) ("oO" . 1) ("0-7" . nil))
+            '(("0"   . 1)
+              ("oO"  . 1)
+              ("0-7" . +))
+            3 ;; Number group.
             amount 8)
 
            ;; Find hex literals.
            (evil-numbers--search-and-replace
-            '(("0" . 1) ("xX" . 1) ("[:xdigit:]" . nil))
+            '(("0"          . 1)
+              ("xX"         . 1)
+              ("[:xdigit:]" . +))
+            3 ;; Number group.
             amount 16)
 
            ;; Find superscript literals.
@@ -309,66 +318,120 @@ decimal: [0-9]+, e.g. 42 or 23"
      ;; Skip format specifiers and interpret as boolean.
      (<= 0 (skip-chars-forward "bBoOxX"))))))
 
-(defun evil-numbers--search-and-replace (char-skip-list inc base)
+(defun evil-numbers--match-from-skip-chars (skip-chars dir do-check do-match)
+  "Match SKIP-CHARS in DIR (-1 or 1).
+
+When DO-CHECK is non-nil, any failure to match returns nil.
+When DO-MATCH is non-nil, match data is set.
+
+Each item in SKIP-CHARS is a cons pair.
+- The first item is the argument to pass to
+  `skip-chars-forward' or `skip-chars-backward'.
+- The second item specifies how many characters to match,
+  Valid values:
+  - Symbol `+' one or more.
+  - Symbol `*' zero or more.
+  - `integerp' this number exactly."
+  (catch 'result
+    (let* ((is-forward (< 0 dir))
+           (skip-chars-fn (if is-forward
+                              #'skip-chars-forward
+                            #'skip-chars-backward))
+           (clamp-fn (if is-forward
+                         #'min
+                       #'max))
+           (limit (if is-forward
+                      (line-end-position)
+                    (line-beginning-position)))
+           (point-init (point))
+           ;; Fill when `do-match' is set.
+           (match-list (list)))
+      (dolist (ch-pair (if is-forward
+                           skip-chars
+                         (reverse skip-chars)))
+        (pcase-let ((`(,ch-skip . ,ch-num) ch-pair))
+
+          ;; Beginning of the match.
+          (when do-match
+            (push (point) match-list))
+
+          (cond
+           ((integerp ch-num)
+            (let ((skipped (funcall
+                            skip-chars-fn
+                            ch-skip
+                            (funcall
+                             clamp-fn
+                             (+ (point) (* ch-num dir)) limit))))
+              (when do-check
+                (unless (eq skipped ch-num)
+                  (throw 'result nil)))))
+           ((memq ch-num (list '+ '*))
+            (let ((skipped (funcall
+                            skip-chars-fn
+                            ch-skip limit)))
+              (when do-check
+                (unless (>= skipped (if (eq ch-num '+) 1 0))
+                  (throw 'result nil)))))
+           (t
+            (error (format "Unknown type %S" ch-skip))))
+
+          ;; End of the match.
+          (when do-match
+            (push (point) match-list))))
+
+      ;; Match 0 for the full range (expected at the beginning).
+      (when do-match
+        (cond
+         (is-forward
+          (setq match-list (nreverse match-list))
+          (push (point) match-list)
+          (push point-init match-list))
+         (t
+          (push point-init match-list)
+          (push (point) match-list)))
+
+        (set-match-data match-list)))
+    t))
+
+(defun evil-numbers--search-and-replace (skip-chars num-group inc base)
   "Perform the increment/decrement on the current line.
 
-Argument CHAR-SKIP-LIST is a list of cons pairs each containing
-characters to skip and the number of characters expected to skip,
+For SKIP-CHARS docs see `evil-numbers--match-from-skip-chars'.
+NUM-GROUP is the match group used to evaluate the number.
 
 When all characters are found in sequence,
 replace number incremented by INC in BASE and return non-nil."
-  (catch 'result
-    (save-excursion
-      ;; Skip backwards (as needed).
-      (let ((limit-min (line-beginning-position)))
-        (dolist (ch-pair (reverse char-skip-list))
-          (pcase-let ((`(,ch-skip . ,ch-num) ch-pair))
-            ;; Allow zero skipped as the cursor may not
-            ;; be at the end of the number.
-            (skip-chars-backward
-             ch-skip
-             (if ch-num
-                 (max (- (point) ch-num) limit-min)
-               limit-min)))))
-
-      ;; Skip forwards.
-      (let ((limit-max (line-end-position))
-            (pt-prev (point)))
-        (dolist (ch-pair char-skip-list)
-          (pcase-let ((`(,ch-skip . ,ch-num) ch-pair))
-            (setq pt-prev (point))
-            (let ((skipped
-                   (skip-chars-forward
-                    ch-skip
-                    (if ch-num
-                        (min (+ (point) ch-num) limit-max)
-                      limit-max))))
-              ;; Every step must succeed.
-              ;; While optional characters could be supported,
-              ;; currently they aren't needed.
-              ;;
-              ;; If a number was given, ensure it matches,
-              ;; otherwise just check the number isn't zero.
-              (when (if ch-num
-                        (not (eq ch-num skipped))
-                      (zerop skipped))
-                (throw 'result nil)))))
-
-        ;; It just so happens the last item in the list always
-        ;; matches the numbers to read.
-        (set-match-data (list pt-prev (point)))))
-
-    (goto-char (match-end 0))
-    (replace-match
-     (evil-numbers--format
-      (+ inc (string-to-number (match-string 0) base))
-      (if evil-numbers/padDefault
-          (- (match-end 0) (match-beginning 0))
-        1)
-      base))
-    ;; Moves point one position back to conform with VIM.
-    (forward-char -1)
-    t))
+  (save-match-data
+    (when (save-excursion
+            ;; Skip backwards (as needed), there may be no
+            ;; characters to skip back, so don't check the result.
+            (evil-numbers--match-from-skip-chars skip-chars -1 nil nil)
+            ;; Skip forwards from the beginning, setting match data.
+            (evil-numbers--match-from-skip-chars skip-chars 1 t t))
+
+      (goto-char (match-end num-group))
+      (let* ((num-prev
+              (string-to-number
+               (match-string num-group)
+               base))
+             (num-next (+ inc num-prev))
+             (str-next
+              (evil-numbers--format
+               num-next
+               (if evil-numbers/padDefault
+                   (- (match-end num-group)
+                      (match-beginning num-group))
+                 1)
+               base)))
+
+        ;; Replace the number.
+        (replace-match str-next t t nil num-group))
+
+      ;; Moves point one position back to conform with VIM.
+      (forward-char -1)
+
+      t)))
 
 (defun evil-numbers--format (num width base)
   "Format NUM with at least WIDTH space in BASE."

Reply via email to