2009-11-12

copying lines without selecting them

When I'm programming, I often need to copy a line. Normally, this requires me to first select ('mark') the line I want to copy. That does not seem like a big deal, but when I'm in the 'flow' I want to avoid any little obstacle that can slow me down.

So, how can I copy the current line without selection? I found a nice trick by MacChan on EmacsWiki to accomplish this. It also adds ta function to kill (cut) the current line (similar to kill-line (C-k), but kills the whole line, not just from point (cursor) to the end.

The code below simply embellishes the normal functions with the functionality 'if nothing is selected, assume we mean the current line'. The key bindings stay the same (M-w, C-w).

To enable this, put the following in your .emacs:

(defadvice kill-ring-save (before slick-copy activate compile) "When called
  interactively with no active region, copy a single line instead."
  (interactive (if mark-active (list (region-beginning) (region-end)) (message
  "Copied line") (list (line-beginning-position) (line-beginning-position
  2)))))

(defadvice kill-region (before slick-cut activate compile)
  "When called interactively with no active region, kill a single line instead."
  (interactive
    (if mark-active (list (region-beginning) (region-end))
      (list (line-beginning-position)
        (line-beginning-position 2)))))

It also shows the power of Emacs-Lisp with the defadvice-macro – see the fine documentation. Using defadvice, you can 'decorate' any function with your own modifications. This great power should be used with caution, of course, as to not break other usage that assumes the undecorated versions. In this case, that seem unlikely. And note that the 'advise' only applies when the functions are called interactively.

14 comments:

Oscar said...

Nice! I've actually been looking for this, but the solutions I've encountered earlier haven't worked as I'd hoped. Thanks! (:

Now, one of the few things left to fix is the opposite to C-k, that is, delete everything from this point to the beginning of the line. The quest goes on...

djcb said...

@Oscar: see the docs for 'kill-line'.
"With zero argument, kills the text before point on the current line."

So
M-0 C-k
will delete from point to beginning.

Oscar said...

Oh, thanks! Again :)

I even managed to bind M-0 C-k to M-k without too much effort (I'm still a newbie in this world):

(global-set-key (kbd "M-k") (lambda () (interactive) (kill-line 0)))

draxil said...

Wow, that is so obvious and yet so useful! Thanks!

vinhdizzo said...

perfect solution! i've seen many attempts at this before. i never implemented any of them because i didn't know what to bind the kill line and copy line commands to. i wanted to bind to one key-stroke like C-k, but ran out of things to bind to...and nothing intuitive came to mind. this is AWESOME as it saves me from binding to new keys. kudos!

Anonymous said...

useless for those which don't use transient-mark-mode. Also, it seems that you should be using `use-region-p' for you test. From the Elisp manual:


-- Variable: mark-active
The mark is active when this variable is non-`nil'. This variable is always buffer-local in each buffer. Do _not_ use the value of this variable to decide whether a command that normally operates on text near point should operate on the region instead. Use the function `use-region-p' for that (*note The Region::).

Anonymous said...

Funny to find this post now... I just made something similar today.

Enjoy!


(defun smart-copy ()
"Copy word at point, or line if called twice, or region if transient-mark active."
(interactive)
(if (eq last-command 'smart-copy)
(progn (kill-ring-save (line-beginning-position) (line-end-position))
(message "Line pushed to kill ring"))
(save-excursion
(if (not mark-active)
(progn
(mark-word)
(backward-word)
(message "Word pushed to kill ring")))
(kill-ring-save (region-beginning) (region-end)))))

Joakim Hårsman said...

You can also use C-S-Backspace to kill the entire line, it's bound to kill-whole-line in Emacs 23, and I think in 22 as well.

But that's a bit more cumbersome than C-w if you use transient mark mode anyway.

aap said...

defadvice rocks. I just noticed it this week, while searching for a way to allow perldb mode to interpret environment variables in the command line you give it when starting. I wound up with this:

(defadvice start-process (around start-process-subst-vars)
"Substitute env vars in `start-process`."
(let ((arglist (ad-get-args 2))
new-arglist
)
(setq new-arglist
(mapcar 'substitute-in-file-name arglist))
(ad-set-args 2 new-arglist)
)
ad-do-it)
(ad-activate 'start-process)

Ferk said...

thanks! that's great.

But how do I make it so that when I press the M-w it goes to the next line (so that I can copy multiple lines)?

I tried adding (next-line) to the if, but it says "wrong number of arguments",why is that? :S


call-interactively: Wrong number of arguments: #[(beg end) "ÃÄ
\"‰)‡" [ad-return-value beg end nil ad-Orig-kill-ring-save] 4 2088129 (byte-code "ƒ

Ferk said...

ok.. I know now what was I doing wrong, sorry.

Now I added the "next-line" before the list creation and changed line-beggining-position accordinglybut it seems it doesn't have the behavior that killing multiple lines have. It overwrites the killed line instead of appending to it.

Dale said...

Select and delete a line: C-a C-SPC C-n C-k. Up until emacs23, 'C-n' always worked on logical lines, independently of line wrapping. By default, this is no longer true which, although I suppose is occasionally useful, will make this sequence fail in the presence of wrapped lines. However, my suggestion would be to turn off this feature, at least in programming modes.

Similarly, to delete the prefix of a line: C-SPC C-a C-k

To delete the end of a line: C-SPC C-e C-k.

I guess I've used these so long that I don't view them as intrusive in any way. Indeed, the sequence above for deleting the prefix of a line is probably quicker to type than M-0 C-k.

Until recently, emacs had no notion of an active region or transient marks, and I tend to think disabling transient mark mode leads to a more fluid style, once you get used to it.

ryanm2215 said...

I got some code originally from emacs-wiki that didn't work, so I modified it and this works for me:

(defun slick-copy ()
(interactive)
"Copy from point to the end of the current line if region is inactive. Otherwise it behaves just like kill-ring-save"
(if (eq mark-active t)
(progn (mark) (kill-ring-save (region-beginning) (region-end)) (message "slick-copy-region"))
(progn (kill-ring-save (point) (line-end-position)) (message "slick-copy-line"))))
;(progn (kill-ring-save (line-beginning-position) (line-end-position)) (message "slick-copy-line"))
;place the above line into slick copy if you would rather grab the entire line you're on
(global-set-key "\M-w" 'slick-copy)

Sébastien said...

Very nice tricks. But I prefer to copy the entire line without the end of line. To do so, I just changed "(line-beginning-position 2)" to "(line-end-position)".