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.
This commit is contained in:
Mattias Engdegård 2023-06-30 18:34:10 +02:00
parent 3ba9f9657f
commit 2195935870
3 changed files with 111 additions and 46 deletions

View file

@ -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.

View file

@ -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)

View file

@ -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.
(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 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 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