branch: elpa/evil-numbers
commit 79daec88d508ffd82d227acac8baf008309c8d81
Author: Campbell Barton <[email protected]>
Commit: Campbell Barton <[email protected]>
Cleanup: sort code into sections
---
evil-numbers.el | 417 +++++++++++++++++++++++++++++---------------------------
1 file changed, 218 insertions(+), 199 deletions(-)
diff --git a/evil-numbers.el b/evil-numbers.el
index c7de3af9e0..8dfffcfe96 100644
--- a/evil-numbers.el
+++ b/evil-numbers.el
@@ -69,6 +69,27 @@
(require 'evil)
+;; ---------------------------------------------------------------------------
+;; Custom Variables
+
+(defgroup evil-numbers nil
+ "Support number increment/decrement."
+ :group 'convenience)
+
+(define-obsolete-variable-alias
+ 'evil-numbers/padDefault 'evil-numbers-pad-default "evil-numbers v0.6")
+
+;;;###autoload
+(defcustom evil-numbers-pad-default nil
+ "Whether numbers are padded by default."
+ :group 'evil-numbers
+ :type 'boolean
+ :options '(nil t))
+
+
+;; ---------------------------------------------------------------------------
+;; Internal Variables
+
(defconst evil-numbers--chars-superscript "⁰¹²³⁴⁵⁶⁷⁸⁹")
(defconst evil-numbers--chars-subscript "₀₁₂₃₄₅₆₇₈₉")
@@ -96,19 +117,128 @@
(aref evil-numbers--chars-subscript i)))
(number-sequence 0 9)))))
-(defgroup evil-numbers nil
- "Support number increment/decrement."
- :group 'convenience)
-(define-obsolete-variable-alias
- 'evil-numbers/padDefault 'evil-numbers-pad-default "evil-numbers v0.6")
+;; ---------------------------------------------------------------------------
+;; Internal Utilities
+;;
+;; Not directly related to incrementing numbers.
-;;;###autoload
-(defcustom evil-numbers-pad-default nil
- "Whether numbers are padded by default."
- :group 'evil-numbers
- :type 'boolean
- :options '(nil t))
+(defun evil-numbers--format-binary (number &optional width fillchar)
+ "Format NUMBER as binary.
+Fill up to WIDTH with FILLCHAR (defaults to ?0) if binary
+representation of NUMBER is smaller."
+ (let (nums
+ (fillchar (or fillchar ?0)))
+ (while (> number 0)
+ (push (number-to-string (% number 2)) nums)
+ (setq number (truncate number 2)))
+ (let ((len (length nums)))
+ (apply #'concat
+ (if (and width (< len width))
+ (make-string (- width len) fillchar)
+ "")
+ nums))))
+
+(defun evil-numbers--format (num width base)
+ "Format NUM with at least WIDTH space in BASE."
+ (cond
+ ((= base 2) (evil-numbers--format-binary num width))
+ ((= base 8) (format (format "%%0%do" width) num))
+ ((= base 16) (format (format "%%0%dX" width) num))
+ ((= base 10) (format (format "%%0%dd" width) num))
+ (t "")))
+
+(defun evil-numbers--match-from-skip-chars
+ (match-chars dir limit do-check do-match)
+ "Match MATCH-CHARS in DIR (-1 or 1), until LIMIT.
+
+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 MATCH-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))
+ (point-init (point))
+ ;; Fill when `do-match' is set.
+ (match-list (list)))
+
+ ;; Sanity check.
+ (when (if is-forward (> (point) limit) (< (point) limit))
+ (error "Limit is on wrong side of point (internal error)"))
+
+ (dolist (ch-pair (if is-forward
+ match-chars
+ (reverse match-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)))))
+ ((eq ch-num '+)
+ (let ((skipped
+ (funcall
+ skip-chars-fn
+ ch-skip limit)))
+ (when do-check
+ (unless (>= skipped 1)
+ (throw 'result nil)))))
+
+ ;; No length checking needed as zero is acceptable.
+ ;; Skip these characters if they exist.
+ ((eq ch-num '*)
+ (funcall
+ skip-chars-fn
+ ch-skip
+ limit))
+ ((eq ch-num '\?)
+ (funcall
+ skip-chars-fn
+ ch-skip
+ (funcall clamp-fn (+ (point) dir) limit)))
+ (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--swap-alist (alist)
"Swap association list ALIST."
@@ -138,6 +268,67 @@
(evil-numbers--translate-with-alist
(evil-numbers--swap-alist evil-numbers--subscript-alist) x))
+
+;; ---------------------------------------------------------------------------
+;; Internal Implementation
+
+(defun evil-numbers--inc-at-pt-impl-with-match-chars
+ (match-chars
+ sign-group num-group
+ amount base
+ beg end
+ padded
+ decode-fn encode-fn)
+ "Perform the increment/decrement on the current line.
+
+For MATCH-CHARS docs see `evil-numbers--match-from-skip-chars'.
+NUM-GROUP is the match group used to evaluate the number.
+SIGN-GROUP is the match group used for the sign ('-' or '+').
+
+When PADDED is non-nil,
+the number keeps it's current width (with leading zeroes).
+
+When all characters are found in sequence,
+replace number incremented by AMOUNT in BASE and return non-nil."
+ (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 match-chars -1 beg nil nil)
+ ;; Skip forwards from the beginning, setting match data.
+ (evil-numbers--match-from-skip-chars match-chars 1 end t t))
+
+ (goto-char (match-end num-group))
+ (let* ((num-prev
+ (string-to-number
+ (funcall decode-fn
+ (concat (match-string sign-group)
+ (match-string num-group)))
+ base))
+ (num-next (+ amount num-prev))
+ (str-next
+ (evil-numbers--format
+ (abs num-next)
+ (if padded
+ (- (match-end num-group)
+ (match-beginning num-group))
+ 1)
+ base)))
+
+ ;; Replace the sign (as needed).
+ (cond
+ ;; From negative to positive.
+ ((and (< num-prev 0) (not (< num-next 0)))
+ (replace-match "" t t nil sign-group))
+ ;; From positive to negative.
+ ((and (not (< num-prev 0)) (< num-next 0))
+ (replace-match (funcall encode-fn "-") t t nil sign-group)))
+
+ ;; Replace the number.
+ (replace-match (funcall encode-fn str-next) t t nil num-group))
+
+ t)))
+
(defun evil-numbers--inc-at-pt-impl (amount beg end padded)
"Increment the number at the current POINT by AMOUNT limited by BEG and END.
@@ -147,7 +338,7 @@ Return non-nil on success, leaving the point at the end of
the number."
(or
;; Find binary literals:
;; 0[bB][01]+, e.g. 0b101 or 0B0
- (evil-numbers--search-and-replace
+ (evil-numbers--inc-at-pt-impl-with-match-chars
'(("+-" . \?)
("0" . 1)
("bB" . 1)
@@ -159,7 +350,7 @@ Return non-nil on success, leaving the point at the end of
the number."
;; Find octal literals:
;; 0[oO][0-7]+, e.g. 0o42 or 0O5
- (evil-numbers--search-and-replace
+ (evil-numbers--inc-at-pt-impl-with-match-chars
'(("+-" . \?)
("0" . 1)
("oO" . 1)
@@ -171,7 +362,7 @@ Return non-nil on success, leaving the point at the end of
the number."
;; Find hex literals.
;; 0[xX][0-9a-fA-F]+, e.g. 0xBEEF or 0Xcafe
- (evil-numbers--search-and-replace
+ (evil-numbers--inc-at-pt-impl-with-match-chars
'(("+-" . \?)
("0" . 1)
("xX" . 1)
@@ -183,7 +374,7 @@ Return non-nil on success, leaving the point at the end of
the number."
;; Find decimal literals:
;; [0-9]+, e.g. 42 or 23.
- (evil-numbers--search-and-replace
+ (evil-numbers--inc-at-pt-impl-with-match-chars
'(("+-" . \?)
("0123456789" . +))
1 ;; Sign group.
@@ -192,7 +383,7 @@ Return non-nil on success, leaving the point at the end of
the number."
#'identity #'identity)
;; Find decimal literals (super-script).
- (evil-numbers--search-and-replace
+ (evil-numbers--inc-at-pt-impl-with-match-chars
`(("⁺⁻" . \?)
(,evil-numbers--chars-superscript . +))
1 ;; Sign group.
@@ -201,7 +392,7 @@ Return non-nil on success, leaving the point at the end of
the number."
#'evil-numbers--decode-super #'evil-numbers--encode-super)
;; Find decimal literals (sub-script).
- (evil-numbers--search-and-replace
+ (evil-numbers--inc-at-pt-impl-with-match-chars
`(("₊₋" . \?)
(,evil-numbers--chars-subscript . +))
1 ;; Sign group.
@@ -215,13 +406,13 @@ Return non-nil on success, leaving the point at the end
of the number."
Keep padding when PADDED is non-nil.
Return non-nil on success, leaving the point at the end of the number."
- (save-match-data
- ;; Search for any text that might be part of a number,
- ;; if `evil-numbers--search-and-replace' cannot parse it - that's fine,
- ;; keep searching until `end'
- ;; This avoids doubling up on number parsing logic.
- (catch 'result
+ (catch 'result
+ (save-match-data
(let ((point-last (1- (point))))
+ ;; Search for any text that might be part of a number,
+ ;; if `evil-numbers--search-and-replace' cannot parse it - that's fine,
+ ;; keep searching until `end'
+ ;; This avoids doubling up on number parsing logic.
(while (< point-last (point))
(when (evil-numbers--inc-at-pt-impl
amount
@@ -244,6 +435,10 @@ Return non-nil on success, leaving the point at the end of
the number."
end t)
(throw 'result nil)))))))
+
+;; ---------------------------------------------------------------------------
+;; Public Functions
+
;;;###autoload (autoload 'evil-numbers/inc-at-pt "evil-numbers" nil t)
(evil-define-operator evil-numbers/inc-at-pt
(amount beg end type &optional incremental padded)
@@ -348,181 +543,5 @@ on."
(interactive "*<c><R>")
(evil-numbers/inc-at-pt (- (or amount 1)) beg end type 'incremental padded))
-;;; Utilities.
-
-(defun evil-numbers--match-from-skip-chars
- (skip-chars dir limit do-check do-match)
- "Match SKIP-CHARS in DIR (-1 or 1), until LIMIT.
-
-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))
- (point-init (point))
- ;; Fill when `do-match' is set.
- (match-list (list)))
-
- ;; Sanity check.
- (when (if is-forward (> (point) limit) (< (point) limit))
- (error "Limit is on wrong side of point (internal error)"))
-
- (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)))))
- ((eq ch-num '+)
- (let ((skipped
- (funcall
- skip-chars-fn
- ch-skip limit)))
- (when do-check
- (unless (>= skipped 1)
- (throw 'result nil)))))
-
- ;; No length checking needed as zero is acceptable.
- ;; Skip these characters if they exist.
- ((eq ch-num '*)
- (funcall
- skip-chars-fn
- ch-skip
- limit))
- ((eq ch-num '\?)
- (funcall
- skip-chars-fn
- ch-skip
- (funcall clamp-fn (+ (point) dir) limit)))
- (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
- sign-group num-group
- amount base
- beg end
- padded
- decode-fn encode-fn)
- "Perform the increment/decrement on the current line.
-
-For SKIP-CHARS docs see `evil-numbers--match-from-skip-chars'.
-NUM-GROUP is the match group used to evaluate the number.
-SIGN-GROUP is the match group used for the sign ('-' or '+').
-
-When PADDED is non-nil,
-the number keeps it's current width (with leading zeroes).
-
-When all characters are found in sequence,
-replace number incremented by AMOUNT in BASE and return non-nil."
- (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 beg nil nil)
- ;; Skip forwards from the beginning, setting match data.
- (evil-numbers--match-from-skip-chars skip-chars 1 end t t))
-
- (goto-char (match-end num-group))
- (let* ((num-prev
- (string-to-number
- (funcall decode-fn
- (concat (match-string sign-group)
- (match-string num-group)))
- base))
- (num-next (+ amount num-prev))
- (str-next
- (evil-numbers--format
- (abs num-next)
- (if padded
- (- (match-end num-group)
- (match-beginning num-group))
- 1)
- base)))
-
- ;; Replace the sign (as needed).
- (cond
- ;; From negative to positive.
- ((and (< num-prev 0) (not (< num-next 0)))
- (replace-match "" t t nil sign-group))
- ;; From positive to negative.
- ((and (not (< num-prev 0)) (< num-next 0))
- (replace-match (funcall encode-fn "-") t t nil sign-group)))
-
- ;; Replace the number.
- (replace-match (funcall encode-fn str-next) t t nil num-group))
-
- t)))
-
-(defun evil-numbers--format (num width base)
- "Format NUM with at least WIDTH space in BASE."
- (cond
- ((= base 2) (evil-numbers--format-binary num width))
- ((= base 8) (format (format "%%0%do" width) num))
- ((= base 16) (format (format "%%0%dX" width) num))
- ((= base 10) (format (format "%%0%dd" width) num))
- (t "")))
-
-(defun evil-numbers--format-binary (number &optional width fillchar)
- "Format NUMBER as binary.
-Fill up to WIDTH with FILLCHAR (defaults to ?0) if binary
-representation of NUMBER is smaller."
- (let (nums
- (fillchar (or fillchar ?0)))
- (while (> number 0)
- (push (number-to-string (% number 2)) nums)
- (setq number (truncate number 2)))
- (let ((len (length nums)))
- (apply #'concat
- (if (and width (< len width))
- (make-string (- width len) fillchar)
- "")
- nums))))
-
(provide 'evil-numbers)
;;; evil-numbers.el ends here