summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMattias EngdegÄrd <mattiase@acm.org>2023-06-30 18:34:10 +0200
committerMattias EngdegÄrd <mattiase@acm.org>2023-07-01 12:41:09 +0200
commit2195935870ca173f8b16c4821816f77ecb2d96c3 (patch)
tree07c893ce3c48f4ebbc37414380ce69602c67ce14
parent3ba9f9657fb69de576132a73cbeefdce03ad1c0f (diff)
downloademacs-2195935870ca173f8b16c4821816f77ecb2d96c3.tar.gz
Add duplicate-region-final-position (bug#64185)
* lisp/misc.el (duplicate-region-final-position): New defcustom. (duplicate-dwim): Use it. * lisp/rect.el (rectangle--duplicate-right): Add displacement argument. * test/lisp/misc-tests.el (misc--duplicate-dwim): Extend test.
-rw-r--r--lisp/misc.el37
-rw-r--r--lisp/rect.el25
-rw-r--r--test/lisp/misc-tests.el97
3 files changed, 112 insertions, 47 deletions
diff --git a/lisp/misc.el b/lisp/misc.el
index 718750404b7..fad8d545e11 100644
--- a/lisp/misc.el
+++ b/lisp/misc.el
@@ -105,7 +105,18 @@ Also see the `copy-from-above-command' command."
(forward-line duplicate-line-final-position)
(move-to-column col))))
-(declare-function rectangle--duplicate-right "rect" (n))
+(defcustom duplicate-region-final-position 0
+ "Where the region ends up after duplicating a region with `duplicate-dwim'.
+When 0, leave the region in place.
+When 1, put the region around the first copy.
+When -1, put the region around the last copy."
+ :type '(choice (const :tag "Leave region in place" 0)
+ (const :tag "Put region around first copy" 1)
+ (const :tag "Put region around last copy" -1))
+ :group 'editing
+ :version "29.1")
+
+(declare-function rectangle--duplicate-right "rect" (n displacement))
;; `duplicate-dwim' preserves an active region and changes the buffer
;; outside of it: disregard the region when immediately undoing the
@@ -118,24 +129,40 @@ Also see the `copy-from-above-command' command."
If the region is inactive, duplicate the current line (like `duplicate-line').
Otherwise, duplicate the region, which remains active afterwards.
If the region is rectangular, duplicate on its right-hand side.
-Interactively, N is the prefix numeric argument, and defaults to 1."
+Interactively, N is the prefix numeric argument, and defaults to 1.
+The variables `duplicate-line-final-position' and
+`duplicate-region-final-position' control the position of point
+and the region after the duplication."
(interactive "p")
(unless n
(setq n 1))
(cond
+ ((<= n 0) nil)
;; Duplicate rectangle.
((bound-and-true-p rectangle-mark-mode)
- (rectangle--duplicate-right n)
+ (rectangle--duplicate-right n
+ (if (< duplicate-region-final-position 0)
+ n
+ duplicate-region-final-position))
(setq deactivate-mark nil))
;; Duplicate (contiguous) region.
((use-region-p)
(let* ((beg (region-beginning))
(end (region-end))
- (text (buffer-substring beg end)))
+ (text (buffer-substring beg end))
+ (pt (point))
+ (mk (mark)))
(save-excursion
(goto-char end)
- (duplicate--insert-copies n text)))
+ (duplicate--insert-copies n text))
+ (let* ((displace (if (< duplicate-region-final-position 0)
+ n
+ duplicate-region-final-position))
+ (d (* displace (- end beg))))
+ (unless (zerop d)
+ (push-mark (+ mk d))
+ (goto-char (+ pt d)))))
(setq deactivate-mark nil))
;; Duplicate line.
diff --git a/lisp/rect.el b/lisp/rect.el
index 5ff821abb3f..8dc188b1de0 100644
--- a/lisp/rect.el
+++ b/lisp/rect.el
@@ -930,8 +930,9 @@ Ignores `line-move-visual'."
(mapc #'delete-overlay (nthcdr 5 rol))
(setcar (cdr rol) nil)))
-(defun rectangle--duplicate-right (n)
- "Duplicate the rectangular region N times on the right-hand side."
+(defun rectangle--duplicate-right (n displacement)
+ "Duplicate the rectangular region N times on the right-hand side.
+Leave the region moved DISPLACEMENT region-wide steps to the right."
(let ((cols (rectangle--pos-cols (point) (mark))))
(apply-on-rectangle
(lambda (startcol endcol)
@@ -940,16 +941,22 @@ Ignores `line-move-visual'."
(move-to-column endcol t)
(dotimes (_ n)
(insert (cadr lines)))))
- (region-beginning) (region-end))
- ;; Recompute the rectangle state; no crutches should be needed now.
- (let ((p (point))
- (m (mark)))
+ (min (point) (mark))
+ (max (point) (mark)))
+ ;; Recompute the rectangle state.
+ (let* ((p (point))
+ (m (mark))
+ (point-col (car cols))
+ (mark-col (cdr cols))
+ (d (* displacement (abs (- point-col mark-col)))))
(rectangle--reset-crutches)
(goto-char m)
- (move-to-column (cdr cols) t)
- (set-mark (point))
+ (move-to-column (+ mark-col d) t)
+ (if (= d 0)
+ (set-mark (point))
+ (push-mark (point)))
(goto-char p)
- (move-to-column (car cols) t))))
+ (move-to-column (+ point-col d) t))))
(provide 'rect)
diff --git a/test/lisp/misc-tests.el b/test/lisp/misc-tests.el
index ea27ea1653b..b9bafe4bd11 100644
--- a/test/lisp/misc-tests.el
+++ b/test/lisp/misc-tests.el
@@ -24,6 +24,7 @@
;;; Code:
(require 'ert)
+(require 'misc)
(defmacro with-misc-test (original result &rest body)
(declare (indent 2))
@@ -113,40 +114,70 @@
(require 'rect)
(ert-deftest misc--duplicate-dwim ()
- ;; Duplicate a line.
- (with-temp-buffer
- (insert "abc\ndefg\nh\n")
- (goto-char 7)
- (duplicate-dwim 2)
- (should (equal (buffer-string) "abc\ndefg\ndefg\ndefg\nh\n"))
- (should (equal (point) 7)))
+ (let ((duplicate-line-final-position 0)
+ (duplicate-region-final-position 0))
+ ;; Duplicate a line.
+ (dolist (final-pos '(0 -1 1))
+ (ert-info ((prin1-to-string final-pos) :prefix "final-pos: ")
+ (with-temp-buffer
+ (insert "abc\ndefg\nh\n")
+ (goto-char 7)
+ (let ((duplicate-line-final-position final-pos))
+ (duplicate-dwim 3))
+ (should (equal (buffer-string) "abc\ndefg\ndefg\ndefg\ndefg\nh\n"))
+ (let ((delta (* 5 (if (< final-pos 0) 3 final-pos))))
+ (should (equal (point) (+ 7 delta)))))))
+
+ ;; Duplicate a region.
+ (dolist (final-pos '(0 -1 1))
+ (ert-info ((prin1-to-string final-pos) :prefix "final-pos: ")
+ (with-temp-buffer
+ (insert "abCDEFghi")
+ (set-mark 3)
+ (goto-char 7)
+ (transient-mark-mode)
+ (should (use-region-p))
+ (let ((duplicate-region-final-position final-pos))
+ (duplicate-dwim 3))
+ (should (equal (buffer-string) "abCDEFCDEFCDEFCDEFghi"))
+ (should (region-active-p))
+ (let ((delta (* 4 (if (< final-pos 0) 3 final-pos))))
+ (should (equal (point) (+ 7 delta)))
+ (should (equal (mark) (+ 3 delta)))))))
+
+ ;; Duplicate a rectangular region (sparse).
+ (with-temp-buffer
+ (insert "x\n>a\n>bcde\n>fg\nyz\n")
+ (goto-char 4)
+ (rectangle-mark-mode)
+ (goto-char 15)
+ (rectangle-forward-char 1)
+ (duplicate-dwim)
+ (should (equal (buffer-string) "x\n>a a \n>bcdbcde\n>fg fg \nyz\n"))
+ (should (equal (point) 24))
+ (should (region-active-p))
+ (should rectangle-mark-mode)
+ (should (equal (mark) 4)))
+
+ ;; Idem (dense).
+ (dolist (final-pos '(0 -1 1))
+ (ert-info ((prin1-to-string final-pos) :prefix "final-pos: ")
+ (with-temp-buffer
+ (insert "aBCd\neFGh\niJKl\n")
+ (goto-char 2)
+ (rectangle-mark-mode)
+ (goto-char 14)
+ (let ((duplicate-region-final-position final-pos))
+ (duplicate-dwim 3))
+ (should (equal (buffer-string)
+ "aBCBCBCBCd\neFGFGFGFGh\niJKJKJKJKl\n"))
+ (should (region-active-p))
+ (should rectangle-mark-mode)
+ (let ((hdelta (* 2 (if (< final-pos 0) 3 final-pos)))
+ (vdelta 12))
+ (should (equal (point) (+ 14 vdelta hdelta)))
+ (should (equal (mark) (+ 2 hdelta)))))))))
- ;; Duplicate a region.
- (with-temp-buffer
- (insert "abc\ndef\n")
- (set-mark 2)
- (goto-char 7)
- (transient-mark-mode)
- (should (use-region-p))
- (duplicate-dwim)
- (should (equal (buffer-string) "abc\ndebc\ndef\n"))
- (should (equal (point) 7))
- (should (region-active-p))
- (should (equal (mark) 2)))
-
- ;; Duplicate a rectangular region.
- (with-temp-buffer
- (insert "x\n>a\n>bcde\n>fg\nyz\n")
- (goto-char 4)
- (rectangle-mark-mode)
- (goto-char 15)
- (rectangle-forward-char 1)
- (duplicate-dwim)
- (should (equal (buffer-string) "x\n>a a \n>bcdbcde\n>fg fg \nyz\n"))
- (should (equal (point) 24))
- (should (region-active-p))
- (should rectangle-mark-mode)
- (should (equal (mark) 4))))
(provide 'misc-tests)
;;; misc-tests.el ends here