Relative copy and paste in vim
Sometimes there seems to be so much hidden power in vim that it’s just stunning. It’s like there’s a hidden reservoir of power just underneath the surface of the commands one uses every day.
A case in point: I was recently editing a document where I often needed to
copy lines of text from one location to another, and although I was using
relative jumps (e.g. via <relative-number>j
or <relative-number>k
) to
get to the locations I needed to go within the document, yanking the text
and then moving to where I needed to paste the text, and pasting it. After
a while I thought “there must be an easier way!”. And there is! It took a
while for me to put the correct search term into Google and StackOverflow,
but I eventually stumbled on the solution:
:t
It’s likely that you’re laughing now at how overly concise and cryptic vim can be. Yes, vim has this reputation, however this level of concision can be so wonderfully powerful; I’m really pleased that I found this seemingly dusty corner of vim’s features.
There’s more of a story here, though. Many vim users know that copy and
paste is performed by first yanking (copying) a piece of text with e.g. yy
(yank a line) and then pasting it with either p
(paste after the cursor)
or P
(paste before the cursor). One also knows that j
moves down in
the file and k
moves up in the file. These commands are sufficient for a
large number of editing tasks. However, for the use case mentioned above,
only using this basic set results in a command sequence like this:
kkkkkkkkkkkkyyjjjjjjjjjjjjjjjjjjjP
which isn’t particularly efficient (and that’s putting it mildly!).
In my case, this was the kind of thing I used to do. Nevertheless, after
having been introduced to the relativenumber
setting
I was able to improve upon this sequence of commands and consequently the
abomination above now became:
12kyy19jP
and this pattern has served me well for many years. Of course, right up until that point in time where I had to do this really often and then I wished for something much more concise. Obviously it had never been sufficiently annoying for me to consider changing my habits. But what was the more concise solution? Time to turn to Dr. Google.
Unfortunately, searching for “copy and paste in vim” only turned up beginner articles about what I already knew! Talk about frustrating! Nevertheless, after lots of trying and many search term permutations along the lines of “relative copy paste vim”, I eventually found the solution to my problem on Unix StackExchange.
I found that the command can be simplified to:
:-12copy.<cr>
which makes the assumption that the cursor is already positioned on the line above where one wants to paste the copied line.
Let’s disect this command a bit.
- The colon (
:
) means that one is entering command-line mode. - The minus sign (
-
) means that the following number refers to the number of lines above the current position; a plus sign (+
), therefore, signifies copying from a certain number of lines below the current cursor position. - Thus,
-12
means “twelve lines above the current cursor position”. -
copy
is the editing command to be executed. - The dot (
.
) is, technically speaking, the address below which to paste the copied line. Dot (.
) basically means “here” in vim-speak, so practically speaking it means to paste the copied line after “here”, which is the current line in this context. - Finally, the
<cr>
just means that one has to hit enter (i.e. carriage return) for the command as a whole to be executed.
So how did I get to :t
being the solution to my problem? Well, it turns
out that :t
is just a synonym for
:copy
(which can be shortened to :co
, but why use two characters when one will
do? ). As mentioned in a comment to the Unix StackExchange
answer, the :t
command is a
left-over from ed
, the
first editor on Unix systems. Although today one thinks of copying text
from one place to another (hence co
for the copy command), the t
means
transfer,
which in essence is what a copy-paste operation is.
If you’re looking for this information in the vim documentation, you’ll find
it as part of the copy-move
documentation,
which contains a huge amount of information. By the way, you can get
directly to the documentation for :copy
with the command :help :copy
from within vim. Also, if you’re interested in digging deeper into vim
commands that use ranges, be sure to have a look at the command
ranges (:help 10.3
) and the ex
command-line ranges
documentation (:help
[range]
).
So, in the end, the required command is:
:-12t.<cr>
But what if the cursor isn’t located on the line above where we want the
copied text to be pasted? What if it’s, say, located somewhere between the
copy and the paste locations? Well, instead of using dot (.
), just
specify the relative number of lines to the location where the text should
be pasted after. In other words, to copy (erm, transfer) the line from 12
lines above to the line after 19 lines below, use:
:-12t+19<cr>
Somehow, the concision and generality of this command really impressed me. It certainly helped me perform fairly repetitive editing tasks more easily and gave me the opportunity to learn and look more deeply into the vim documentation. I hope that my new knowledge helped you in some way too!
Postscript
While researching some of the links for this post I found out that Drew Neil had already discussed this topic in episode 40 of his excellent vimcasts videos. I’m fairly sure that I would have seen this episode, but obviously I needed to “discover” it again. Oops.
As Drew mentions in his Core Vim Class:
You only have a finite number of keystrokes before you die. Make them count!
Update
Many thanks to -romainl-
for
pointing out and correcting errors in this post and to
--Antony
for suggesting the links
to the range documentation!
Support
If you liked this post and want to see more like this, please buy me a coffee!