branch: externals/shift-number
commit 95b49885b5c58e5a520659b148d50912f3f58baa
Author: Campbell Barton <[email protected]>
Commit: Campbell Barton <[email protected]>

    Support for shifting numbers in regions
---
 shift-number.el | 182 ++++++++++++++++++++++++++++++++++++--------------------
 1 file changed, 116 insertions(+), 66 deletions(-)

diff --git a/shift-number.el b/shift-number.el
index f111c1ced4..21959d206e 100644
--- a/shift-number.el
+++ b/shift-number.el
@@ -92,16 +92,14 @@ Return the region replaced."
       (goto-char (+ (point) i-end-ofs)))
     (cons beg (+ beg (length str)))))
 
-(defun shift-number--in-regexp-p (regexp)
-  "Return non-nil, if point is inside REGEXP on the current line."
+(defun shift-number--in-regexp-p (regexp pos limit-beg limit-end)
+  "Return non-nil, if POS is inside REGEXP on the current line."
   ;; The code originates from `org-at-regexp-p'.
   (save-excursion
-    (let ((pos (point))
-          (end (line-end-position))
-          found
-          exit)
-      (beginning-of-line)
-      (while (and (not (or exit found)) (re-search-forward regexp end t))
+    (let ((found nil)
+          (exit nil))
+      (goto-char limit-beg)
+      (while (and (not (or exit found)) (re-search-forward regexp limit-end t))
         (cond
          ((> (match-beginning 0) pos)
           (setq exit t))
@@ -109,95 +107,147 @@ Return the region replaced."
           (setq found t))))
       found)))
 
-(defun shift-number--impl (n)
+(defun shift-number--impl (n pos limit-beg limit-end)
   "Change the number at point by N.
 If there is no number at point, search forward till the end of
-the current line and change it."
+the current line and change it.
+
+Search backwards from LIMIT-BEG for a number overlapping POS.
+Otherwise search forward limited by LIMIT-END."
   ;; The whole number is removed and a new number is inserted in its
   ;; place, so `save-excursion' is not used, as it will put the point at
   ;; the beginning of the number.  Instead, the point is saved and
   ;; restored later.
-  (let ((old-pos (point))
-        (num-bounds
+  (let ((num-bounds
          (save-match-data
            (cond
-            ((or (shift-number--in-regexp-p shift-number-regexp)
-                 (re-search-forward shift-number-regexp (line-end-position)))
+            ((or (and (< limit-beg pos)
+                      (shift-number--in-regexp-p shift-number-regexp pos 
limit-beg limit-end))
+                 (re-search-forward shift-number-regexp limit-end t))
              (cons (match-beginning 1) (match-end 1)))
             (t
              nil)))))
 
-    (unless num-bounds
+    (cond
+     (num-bounds
+      (let* ((beg (car num-bounds))
+             (end (cdr num-bounds))
+             (sign
+              (and shift-number-negative
+                   (cond
+                    ((eq ?- (char-before beg))
+                     -1)
+                    (t
+                     1))))
+             (old-bounds
+              (cons
+               (cond
+                ((eq sign -1)
+                 (1- beg))
+                (t
+                 beg))
+               end))
+
+             (old-num-str (buffer-substring-no-properties beg end))
+             (old-num (string-to-number old-num-str))
+             (new-num (+ old-num (* sign n)))
+
+             (new-num-sign-str "")
+             (new-num-leading-str "")
+             (new-num-str (number-to-string (abs new-num))))
+
+        ;; Handle sign flipping & negative numbers.
+        (when (< new-num 0)
+          (setq sign (- sign)))
+        (when (eq sign -1)
+          (setq new-num-sign-str "-"))
+
+        ;; If there are leading zeros, preserve them keeping the same
+        ;; length of the original number.
+        (when (string-match-p "\\`0" old-num-str)
+          (let ((len-diff (- (length old-num-str) (length new-num-str))))
+            (when (> len-diff 0)
+              (setq new-num-leading-str (make-string len-diff ?0)))))
+
+        ;; Prefer this over delete+insert so as to reduce the undo overhead
+        ;; when numbers are mostly the same.
+        (let* ((new-num-str-full (concat new-num-sign-str new-num-leading-str 
new-num-str))
+               (new-bounds (cons (car old-bounds) (+ (car old-bounds) (length 
new-num-str-full)))))
+
+          (shift-number--replace-in-region new-num-str-full (car old-bounds) 
(cdr old-bounds))
+
+          ;; Result.
+          (cons old-bounds new-bounds))))
+     (t
+      nil))))
+
+(defun shift-number--on-line (n)
+  "Adjust the number N on the current line."
+  (let* ((old-pos (point))
+         (bounds-pair
+          (shift-number--impl n old-pos (line-beginning-position) 
(line-end-position))))
+
+    (unless bounds-pair
       (error "No number on the current line"))
 
-    (let* ((beg (car num-bounds))
-           (end (cdr num-bounds))
-           (sign
-            (and shift-number-negative
-                 (cond
-                  ((eq ?- (char-before beg))
-                   -1)
-                  (t
-                   1))))
-           (replace-bounds
-            (cons
-             (cond
-              ((eq sign -1)
-               (1- beg))
-              (t
-               beg))
-             end))
-
-           (old-num-str (buffer-substring-no-properties beg end))
-           (old-num (string-to-number old-num-str))
-           (new-num (+ old-num (* sign n)))
-
-           (new-num-sign-str "")
-           (new-num-leading-str "")
-           (new-num-str (number-to-string (abs new-num))))
-
-      ;; Handle sign flipping & negative numbers.
-      (when (< new-num 0)
-        (setq sign (- sign)))
-      (when (eq sign -1)
-        (setq new-num-sign-str "-"))
-
-      ;; If there are leading zeros, preserve them keeping the same
-      ;; length of the original number.
-      (when (string-match-p "\\`0" old-num-str)
-        (let ((len-diff (- (length old-num-str) (length new-num-str))))
-          (when (> len-diff 0)
-            (setq new-num-leading-str (make-string len-diff ?0)))))
-
-      ;; Prefer this over delete+insert so as to reduce the undo overhead
-      ;; when numbers are mostly the same.
-      (shift-number--replace-in-region
-       (concat new-num-sign-str new-num-leading-str new-num-str)
-       (car replace-bounds)
-       (cdr replace-bounds))
+    (let* ((old-bounds (car bounds-pair))
+           (new-bounds (cdr bounds-pair))
+           (old-end (cdr old-bounds))
+           (new-end (cdr new-bounds)))
 
       (cond
        ;; If the point was exactly at the end, keep it there.
-       ((eq old-pos end)
-        (setq old-pos (point)))
+       ((eq old-pos old-end)
+        (setq old-pos new-end))
        ;; Prevent the change causing the cursor to "leave" the number,
        ;; allowing for further adjustments.
        (t
-        (setq old-pos (min (point) old-pos))))
+        (setq old-pos (min new-end old-pos))))
+
+      (goto-char old-pos)
+
+      new-end)))
+
+(defun shift-number--on-region-impl (n region-beg region-end)
+  "Shift the numbers N in the region defined.
+REGION-BEG & REGION-END define the region."
+  (save-excursion
+    (let ((bounds-pair nil))
+      (goto-char region-beg)
+      (while (and (setq bounds-pair (shift-number--impl n region-beg 
region-beg region-end)))
+        (let* ((old-bounds (car bounds-pair))
+               (new-bounds (cdr bounds-pair))
+               (old-end (cdr old-bounds))
+               (new-end (cdr new-bounds)))
+
+          ;; Keep contracting the region forward & updating it's end-points.
+          (setq region-beg new-end)
+          (setq region-end (+ region-end (- new-end old-end)))))))
+  region-end)
 
-      (goto-char old-pos))))
+(defun shift-number--on-region (n)
+  "Shift the numbers N on the current region."
+  (shift-number--on-region-impl n (region-beginning) (region-end)))
 
 ;;;###autoload
 (defun shift-number-up (&optional arg)
   "Increase the number at point (or on the current line) by ARG."
   (interactive "p")
-  (shift-number--impl arg))
+  (cond
+   ((region-active-p)
+    (shift-number--on-region arg))
+   (t
+    (shift-number--on-line arg))))
 
 ;;;###autoload
 (defun shift-number-down (&optional arg)
   "Decrease the number at point (or on the current line) by ARG."
   (interactive "p")
-  (shift-number--impl (- arg)))
+  (cond
+   ((region-active-p)
+    (shift-number--on-region (- arg)))
+   (t
+    (shift-number--on-line (- arg)))))
 
 (provide 'shift-number)
 ;; Local Variables:

Reply via email to