2010-01-21

duplicating lines and commenting them

Someone on the Emacs Help mailing list asked for an easy way to duplicate a line
and, optionally, comment-out the first one.

Let's first look at simple duplication of a line. This is a common operation,
and vi-users might use something like Yp for that. In emacs, one way to do
this is by typing C-a C-k C-k C-y C-y, which is actually not as bizarre as
it looks if you try it. When using slick copy, as explained in another post,
it's as easy as M-w C-n C-y; and of course it's easy to define a shorter key
binding.

repeat after me
repeat after me

However, neither of these methods works correctly when you're on the last line
of the buffer (by default at least). Also, it puts the point (cursor) below
the duplicated line, while I'd like to put it at the start of it. It seems we
need something a little smarter.

While we're at it, let's also consider the second question: commenting-out the
first line of the duplicates. This is a quite common thing to do when writing
programs or configuration files; you want to try the effect of a small
variation of a line, but want to keep the original so it can be restored when
the variation turns out not to be as good as expected.

/* for (;;) fork (); */
for (;;) fork ();

I hacked up something quickly to solve both questions, and it has evolved a
little bit since – to answer both of the questions. The bit of weirdness in
the end is because of the special case of the last line in a buffer. It
defines key bindings C-c y for duplicating a line, and C-c c for
duplicating + commenting – but of course you can change those.

(defun djcb-duplicate-line (&optional commentfirst)
  "comment line at point; if COMMENTFIRST is non-nil, comment the original" 
  (interactive)
  (beginning-of-line)
  (push-mark)
  (end-of-line)
  (let ((str (buffer-substring (region-beginning) (region-end))))
    (when commentfirst
    (comment-region (region-beginning) (region-end)))
    (insert-string
      (concat (if (= 0 (forward-line 1)) "" "\n") str "\n"))
    (forward-line -1)))

;; or choose some better bindings....

;; duplicate a line
(global-set-key (kbd "C-c y") 'djcb-duplicate-line)

;; duplicate a line and comment the first
(global-set-key (kbd "C-c c") (lambda()(interactive)(djcb-duplicate-line t)))

7 comments:

Squiddo said...

Just another thought... I didn't want to have to set the mark, so a small change:

(defun djcb-duplicate-line (&optional commentfirst)
"comment line at point; if COMMENTFIRST is non-nil, comment the original"
(interactive)
(beginning-of-line)
(let
((beg (point)))
(end-of-line)
(let ((str (buffer-substring beg (point))))
(when commentfirst
(comment-region beg (point)))
(insert-string
(concat (if (= 0 (forward-line 1)) "" "\n") str "\n"))
(forward-line -1))))

Thanks.

Anonymous said...

nice!

Enchanter said...

I like the one without mark.

But can anyone just modify it as if marked, copy the marked part, if not, copy the whole line.

(I am not good at lisp)

Unknown said...

Change the (interactive) call to (interactive "P") and C-u C-c y will duplicate with comment.

Kyle Sherman said...

I've been using a version of this for a while now. I actually map mine to C-xC-d because I use it so much. After reading this post, I've added the comment option.

This version will leave the cursor on the same column it was before (but on the new line). I prefer this behavior.

Thank you Ola for the interactive hint.

(defun duplicate-line (&optional comment line)
"Duplicate the line containing the point.
\nIf COMMENT is non-nil, also comment out the original line.
If LINE is non-nil, duplicate that line instead."
(interactive "P")
(let ((col (current-column)))
(save-excursion
(when line
(goto-line line))
(let ((line (buffer-substring (point-at-bol) (point-at-eol))))
(when comment
(comment-region (point-at-bol) (point-at-eol)))
(goto-char (point-at-eol))
(if (eobp)
(newline)
(forward-line 1))
(open-line 1)
(insert line)))
(forward-line 1)
(move-to-column col)))

Unknown said...

A line can be killed by just C-S-backspace, which calls kill-whole-line. It will kill the whole line independently of cursor position.

However, as C-k, it doesn't work on the buffer's last line.

Anders Rønningen said...

Thanks!

Would it be useful to have something similar for regions?