yanking by line number in vim - vim

I have a file and I want to do the following.
- copy every n lines starting from m (m,m+n,m+2n, ...)
- copy line number 2, 5, 27, ... by specifying line numbers.
THanks

To copy every N lines, you can use :global with an expression that selects the lines:
:let #a = ''
:g/^/if line('.') % 3 == 0 | yank A | endif
For explicit lines, I would sequentially call the :yank command:
2yank a | 5yank A | 27yank A
This uses yanking into the uppercase register to append to it.

Besides the :g solution, Ingo posted, you can also use an :s command.
First you need to prepare the pattern. For example to explicitly match every third line,
you can use the pattern \%3l\|\%6l\|\%9l, etc.
So first let's save the generated pattern inside a variable (to simplify it a bit, we only consider the first 100 lines):
:let lines=range(3,100,3)
This creates a list of all line numbers, starting from 3 and incrementing by 3, Note, if you need some special line numbers, that don't follow any arithemtic rule, simply define the list as this:
:let lines=[2,5,26,57,99]
Then we need to generate a pattern out of it, which we can use inside an :s command:
:call map(lines, '''\%''.v:val.''l''')
This translates the line numbers into a pattern of the form \%numberl. So we have a pattern matching each desired line, but first we need to initalize a resulting list variable:
:let result = []
We can now feed this to the :s command:
:exe ":%s/". join(lines, '.*\|'). '/\=add(result, submatch(0))/n'
All matching lines are now contained in the list result and can e.g. be copied to the clipboard by using:
:let #+=join(result, "\n")
or you can paste it into a scratch buffer:
:new +exe\ append(0,result)
(Note, that the space between exe and the append call needs to be escaped).
Please also note, that this solution requires at least Vim Patch 7.3.627
Depending on the situation I would either use this method or the one pointed out by Ingo.

Related

Exiting exe mode in a macro

I had a large file I was trying to reformat which involved removing the 2nd to nth repeating sets on 2 to 100 lines per duplicate.
The data looked like
element1.element2.element...field.comment
I wanted to remove the repetition in elements after the first instance so of course I went complicated :) and did a macro something like
In a macro Yanked first element on current line to register p and then processed lines yanking the first element into register o and then doing, still in the macro
:if (#p=!#o)|:.s/paste register p//g|else|:norm! j|endif
Now this worked OK except when it got to a line where #p<>#o the :norm! j part stayed in : mode until I manually escaped once or twice then executed the :norm! j command.
I solved the problem an easier way but would like to know why it was only on the else portion that it wouldn't leave :ex mode.
From :help norm
:norm[al][!] {commands} *:norm* *:normal*
...
This command cannot be followed by another command,
since any '|' is considered part of the command.
...
An alternative is to use |:execute|, which uses an
expression as argument. This allows the use of
printable characters to represent special characters.
Example: >
:exe "normal \<c-w>\<c-w>"
So this would do the trick:
:if (#p=!#o)|:.s/paste register p//g|else|:exe "norm j"|endif

copying just specific lines in a file with vim

Could someone please tell me know how to copy specific lines, for example
Lines 10-20, 22, 24-30 in a file, so I can paste it to another file?
I saw this stackoverflow post as someone had pointed out, however, I'm asking a different questionwhere
Here's a fun little idea. Paste this in your ~/.vimrc:
command! -nargs=* Y :call YankList(<f-args>)
fun! YankList(...)
let yanklist = []
for i in a:000
if match(i, "-") != -1
let split = split(i, "-")
let yanklist = yanklist + range(split[0], split[1])
else
let yanklist = yanklist + i
endif
endfor
call setreg('"', "")
for i in yanklist
call setreg('"', getline(i), "al")
endfor
endfun
Now you can specify lines to yank to the unnamed register. So do:
:Y 10-20 22 24-30
and use p to paste them wherever you want them. (inclusive)
I'd like to edit this post even though it's old to suggest the more "vimmy" way of doing this. See :help usr_10 | 131.
You could do:
10GV20G"ay
22G"AY
24GV30G"Ay
G"ap
Also, if there were some specific pattern that each of these lines contained, then you could grab them by said pattern. Say for example I wanted to yank all lines containing the word "foo", then I could do
:g/foo/y "
Here is a simple solution using Registers.
Using your scenario you provided, needing to yank Lines 10-20, 22, 24-30
Just yank each group with "A".
:10,20y A
:22y A
:24,30y A
At this point you have each of those sets of lines copied to your "A" register. Now you you can use p to paste as you normally would OR you can use "Ap (double quote, Letter of Register, then p to paste just those you yanked with to the A Register.
Read more about Registers Here and Here
Use visual mode, or directly:
:10,20yank
Copy to a new file:
:new | put | 0d
Usually, you'll either have a criterion, e.g. move all lines containing pattern to the end:
:g/pattern/m$
To copy (:copy or :t)
:g/pattern/t$
To yank to a register:
:let #a="" | g/pattern/y A
Now, you can use it wherever you like e.g. "aP to paste it.
If you don't have patterns like that to use, just use text motions, e.g. }:y A to append a block of lines till the next empty line to register a etc.
Edit PS. I thought I'd explain a bit more why I mention m$ to move to the end (a personal favourite of mine):
If you opt to move/copy lines to the end of the file (m$), you can then write them to another file at once. E.g.
:$mark a
:g/pattern/t$
:'a,$w newfile.txt
Copies the lines matching to file newfile.txt. Now delete the copy from the source file:
:'a,$d
Have the both files open - invoke directly from the command line as vim fileone filetwo or open vim and then :e file. You can then switch between them with buffer commands, for two files :bn and :bp are equivalent (buffer next, previous). Then just copy the lines.
This can be done pretty easily: 10G to go to line 10, y10y to copy the next ten lines, then :bn and p to stick it in the other file.

How to diff two lines in an open file in vim?

I occasionally see very long lines in my code that I need to check if they are the same. Is there a way in vim to select two lines and diff them to show any differences between the two?
For example, given the two lines in vim:
AVeryLongReturnType* MyLongClassName:hasAnEvenLongerFunction(That *is, Overloaded *with, Multiple *different, Parameter *lists);
AVeryLongReturnType* MyLongClassName:hasAnEvenLongerFunction(That *is, Overloaded *with, Multiple *different, Parameter *1ists);
I would like vim to tell me that the two lines are in fact different because each spells "lists" differently. Is this possible, and if so, how do I do it?
A quick and dirty solution is to just select both lines and sort them while removing duplicates:
select lines
":sort u"
if only one line remains, both were equal
if both remain, there most be some difference
An undo recovers everything again.
An alternative to #sehe's approach would not require the use of temp files:
funct! DiffTwoTexts(text1, text2)
new
put =a:text1
normal ggdd
diffthis
new
put =a:text2
normal ggdd
diffthis
endfunct
funct! DiffTwoLines(line1, line2)
let text1 = getline(a:line1)
let text2 = getline(a:line2)
call DiffTwoTexts(text1, text2)
endfunct
comma! DiffWithNext call DiffTwoLines('.', line('.') + 1)
This will still be pretty hard to read, since it keeps everything on a single line, so I came up with this modification:
funct! EvalTextPreprocessor(expr, text)
let text = a:text
return eval(a:expr)
endfunct
comma! -nargs=1 DiffWithNextPre call DiffTwoTexts(
\ EvalTextPreprocessor(<q-args>, getline('.')),
\ EvalTextPreprocessor(<q-args>, getline(line('.') + 1)))
This new command takes a vimscript expression as its argument, wherein the variable text refers to whichever line is being preprocessed. So you can call, e.g.
DiffWithNextPre split(text, '[(,)]\zs')
For your sample data, this gives the two buffers
AVeryLongReturnType* MyLongClassName:hasAnEvenLongerFunction(
That *is,
Overloaded *with,
Multiple *different,
Parameter *lists)
;
and
AVeryLongReturnType* MyLongClassName:hasAnEvenLongerFunction(
That *is,
Overloaded *with,
Multiple *different,
Parameter *1ists)
;
Only the lines that start with Parameter are highlighted.
You can even build up from there, creating a command
comma! DiffTwoCFunctionSigs DiffWithNextPre split(text, '[(,)]\s*\zs')
Notice that I modified the regexp a bit so that it will keep trailing spaces at the end of lines. You could get it to ignore them entirely by moving the \s* to after the \zs. See :help /\zs if you're unfamiliar with what that vim-specific RE atom does.
A nicety would be to make the command take a range (see :help command-range), which you could use by diffing the first line of the range with the last line. So then you just visual-select from the first line to the second and call the command.
I used linediff.vim.
This plugin provides a simple command, ":Linediff", which is used to diff two separate blocks of text.
That is not a feature, however it is easily scripted, e.g. in your vimrc:
function! DiffLineWithNext()
let f1=tempname()
let f2=tempname()
exec ".write " . f1
exec ".+1write " . f2
exec "tabedit " . f1
exec "vert diffsplit " . f2
endfunction
This will open the current and next lines in vertical split in another tab.
Note that this code is a sample
it doesn't check whether next line exists (there are any following lines)
it doesn't cleanup the tempfiles created
a nice improvement would be to take a range, or use the '' mark to select the other line
You can leave off the 'vert' in order to have a horizontal split
Map it to something fancy so you don't have to :call it manually:
:nnoremap <F10> :call DiffLineWithNext()^M
you could also just create a new empty window buffer and copy line, then make command:
:windo diffthis
this should open a new window showing the differences of those 2 lines

Prepending a character followed by the line number to every line

I'm hand-editing CNC Gcode text files and need a way to reference locations in the file and on the toolpath.
I want to modify every line in the text file so that it begins with the the upper case letter N followed by the line number, incremented in tens for each successive line, then a whitespace followed by the original text on that line. How can I do this in Vim?
I'm not sure about vi, but (since you're using the vim tag) Vim allows you to accomplish your task as follows:
Adjust the first line by hand (insert a N10 at the beginning of the line), then put the cursor at the beginning of the next line.
Press qb to start recording a macro (the b names the register used to store the macro; feel free to use a different letter -- and definitely do use a different letter if you've got something useful stashed away in b).
Move the cursor upward to the beginning of the previous line (which you have adjusted by hand). Press v to start visual selection mode, then f to move the cursor to the next space on the line (if you use a single space as your whitespace separator, that is; adjust this step if you're using a tab or multiple spaces).
Press y to yank the selected text. This will also remove the visual selection.
Move the cursor to the beginning of the next line. Press P to insert the previously yanked text before the cursor, that is, on the very beginning of the line.
Move the cursor to the numeric part of the line header. Press 10 C-a (1, 0, control + A) to increment that number by 10.
Move the cursor to the beginning of the next line. Press q to stop recording the macro.
Press 10000000 #b to execute the macro 10000000 times or until it hits the end of the file. This should be enough to take care of all the lines in your file, unless it is really huge, in which case use a bigger number.
...or use Vim to write a simple script to do the job in whichever language you like best, then run it from a terminal (or from withing Vim with something like :!./your-script-name). ;-)
The following command will prepend ‘N<line number * 10>’ to every line:
:g/^/exe 'normal! 0iN' . (line('.')*10) . ' '
You can do it easily in Vim with this:
:%s/^/\=line(".")*10 . " "/
This replaces the start of every line with the result of an expression that gives the line number times ten, followed by a space.
I have not timed it, but I suspect it might be noticeably faster than the other Vim solutions.
Cheating answer:
:%!awk '{print "N" NR "0", $0}'
There are two ways to implement that without resorting to external
tools: via a macro or by using Vimscript. In my opinion, the first way
is a little cumbersome (and probably not as effective as the solution
listed below).
The second way can be implemented like this (put the code into your
.vimrc or source it some other way):
function! NumberLines(format) range
let lfmt = (empty(a:format) ? 'N%04d' : a:format[0]) . ' %s'
for lnum in range(a:firstline, a:lastline)
call setline(lnum, printf(lfmt, lnum, getline(lnum)))
endfor
endfunction
The NumberLines function enumerates all lines of the file in a given
range and prepends to each line its number according to the provided
printf-format (N%04d, by default).
To simplify the usage of this function, it is convenient to create
a command that accepting a range of lines to process (the whole file,
by default) and a optional argument for the line number format:
command! -range=% -nargs=? NumberLines <line1>,<line2>call NumberLines([<f-args>])

How do I yank all matching lines into one buffer?

How do you yank all matching lines into a buffer?
Given a file like:
match 1
skip
skip
match 2
match 3
skip
I want to be able issue a command to yank all lines that match a pattern (/^match/ for this example) into a single buffer so that I can put it into another doc, or into a summary or whatever.
The command should wind up with this in a buffer:
match 1
match 2
match 3
My first thought was to try:
:g/^match/y
But I just get the last match. This makes sense, because the :g command is effectively repeating the y for each matching line.
Perhaps there is a way to append a yank to buffer, rather than overwriting it. I couldn't find it.
:g/^match/yank A
This runs the global command to yank any line that matches ^match and put it in register a. Because a is uppercase, instead of just setting the register to the value, it will append to it. Since the global command run the command against all matching lines, as a result you will get all lines appended to each other.
What this means is that you probably want to reset the register to an empty string before starting: :let #a="" or qaq (i.e., recording an empty macro).
And naturally, you can use the same with any named register.
:help registers
:help quote_alpha
:help global
Using Vi/Vim: Ex and Ex-like Commands
:help registers
:help quote_alpha
Specify a capital letter as the register name in order to append to it, like :yank A.
Oh I just realized after commenting above that it's easy to yank matching lines into a temporary buffer...
:r !grep "pattern" file.txt
The simplest solutions come once you've given up on finding them. :)

Resources