diff --git a/etc/NEWS b/etc/NEWS index ca0d602e9ad..aa3b758a815 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -696,7 +696,9 @@ between these modes while the user is inputting a command by hitting 'duplicate-line' duplicates the current line the specified number of times. 'duplicate-dwim' duplicates the region if it is active. If not, it works like 'duplicate-line'. An active rectangular region is -duplicated on its right-hand side. +duplicated on its right-hand side. The new user option +'duplicate-line-final-position' specifies where to move point +after duplicating the line. --- ** Files with the ".eld" extension are now visited in 'lisp-data-mode'. diff --git a/lisp/misc.el b/lisp/misc.el index ca013d5f72f..898fe9dd168 100644 --- a/lisp/misc.el +++ b/lisp/misc.el @@ -63,21 +63,42 @@ copy-from-above-command (+ n (point))))))) (insert string))) +(defcustom duplicate-line-final-position 0 + "Where to put point after duplicating the line with `duplicate-line'. +When 0, leave point on the original line. +When 1, move point to the first new line. +When -1, move point to the last new line. +The same column is preserved after moving to a new line." + :type '(choice (const :tag "Leave point on old line" 0) + (const :tag "Move point to first new line" 1) + (const :tag "Move point to last new line" -1) + (integer)) + :group 'editing + :version "29.1") + ;;;###autoload (defun duplicate-line (&optional n) "Duplicate the current line N times. Interactively, N is the prefix numeric argument, and defaults to 1. +The user option `duplicate-line-final-position' specifies where to +move point after duplicating the line. Also see the `copy-from-above-command' command." (interactive "p") (unless n (setq n 1)) - (let ((line (buffer-substring (line-beginning-position) (line-end-position)))) - (save-excursion - (forward-line 1) - (unless (bolp) - (insert "\n")) - (dotimes (_ n) - (insert line "\n"))))) + (let ((line (buffer-substring (line-beginning-position) (line-end-position))) + (pos (point)) + (col (current-column))) + (forward-line 1) + (unless (bolp) + (insert "\n")) + (dotimes (_ n) + (insert line "\n")) + (unless (< duplicate-line-final-position 0) + (goto-char pos)) + (unless (eq duplicate-line-final-position 0) + (forward-line duplicate-line-final-position) + (move-to-column col)))) (declare-function rectangle--duplicate-right "rect" (n)) diff --git a/test/lisp/misc-tests.el b/test/lisp/misc-tests.el index f1d22e099b9..ea27ea1653b 100644 --- a/test/lisp/misc-tests.el +++ b/test/lisp/misc-tests.el @@ -88,6 +88,20 @@ misc--duplicate-line (duplicate-line 2) (should (equal (buffer-string) "abc\ndefg\ndefg\ndefg\nh\n")) (should (equal (point) 7))) + ;; Duplicate a line (twice) and move point to the first duplicated line. + (with-temp-buffer + (insert "abc\ndefg\nh\n") + (goto-char 7) + (let ((duplicate-line-final-position 1)) (duplicate-line 2)) + (should (equal (buffer-string) "abc\ndefg\ndefg\ndefg\nh\n")) + (should (equal (point) 12))) + ;; Duplicate a line (twice) and move point to the last duplicated line. + (with-temp-buffer + (insert "abc\ndefg\nh\n") + (goto-char 7) + (let ((duplicate-line-final-position -1)) (duplicate-line 2)) + (should (equal (buffer-string) "abc\ndefg\ndefg\ndefg\nh\n")) + (should (equal (point) 17))) ;; Duplicate a non-terminated line. (with-temp-buffer (insert "abc")