prevblank() or startpara() function? - vim

For the purposes of fixing indent/rst.vim I would need a Viml function returning line number of the first line of paragraph (blank line separated set of lines). Does anybody have something like that written?

I guess you want line("'{"). It returns the number of (blank) line before the previous paragraph (or 1 if the paragraph is at the beginning of a file). See :h '{ and :h {.
UPD. If we speak of a "complete" version:
function! StartPara()
let l:lnum = line("'{")
return l:lnum > 1 ? l:lnum + 1 : 1 + empty(getline(1))
"or a shorter but a little more inefficient version:
"return l:lnum + empty(getline(l:lnum))
endfunction
Note that a line containing only spaces is counted as a "paragraph" line, not as a "separator" line. Thus we don't need to match a regex.

I’ve got even better answer on Reddit:
function! get_paragraph_start()
let paragraph_mark_start = getpos("'{")[1]
return getline(paragraph_mark_start) =~ '\S' ? paragraph_mark_start : paragraph_mark_start + 1
endfunction
which is kind of similar to what Matt suggested, but more complete.

Related

VIM: delete strings with the same pattern

I need to find all pairs of strings that have the same pattern.
For example:
another string, that is not interesting
la-di-da-di __pattern__ -di-la-di-la
la-di-da-da-di-la __pattern__ -la-da-li-la
and yet another usual string
So I want to delete strings with __pattern__ inside.
I don't know how to do it just with builtin commands and now I have the function, that doesn't work properly:
function! DelDup(pattern)
echom a:pattern
redir => l:count
execute "normal! :%s/a:pattern//n\<cr>"
redir END
echo l:count
endfunction
Here I try to run ":%s/a:pattern//n" to find the count of occurrences of pattern in the text.
And at the same time I try to put it into the variable "l:count".
Then I tried to echo the count I got, but nothing happens when I try to do it.
So the last my problem in function writing is that I can't write the command execution result to variable.
If you have another solution -- please describe it to me.
Update:
Excuse me for bad description. I want to delete only strings, that has pattern-twins in text.
I'm not sure if I understand your question correctly, but I'm assuming you want to remove all lines where there are at least 2 matches. If that's the case you can use the following command:
:g/\(__pattern__.*\)\{2,}/d
How this works is that it deletes all the lines where there is a match (:g/../d).
The pattern is made up of a group (\(..\)) which needs to be matched at least 2 times (\{2,}). And the pattern has a .* at the end so it matches everything between the matches of the pattern.
There are many ways to count occurrences of a pattern, and I'm quite sure there exist a Q/A on the subject. Let's do it yet another way and chain with the next step. (Yes this is completely obfuscated, but it permits to obtain programmatically the information without the need to parse the localized result of :substitute after redirection.)
" declare a list that contain all matches
let matches = []
" replace each occurrence of the "pattern" with:
" the result of the expression "\=" that can be
" interpreted as the last ([-1]) element of the
" list "matches" returned by the function (add)
" that adds the current match (submatch(0)) to the
" list
:%s/thepattern/\=add(matches, submatch(0))[-1]/gn
" The big caveat of this command is that it modifies
" the current buffer.
" We need something like the following to leave it unmodified:
:g/thepattern/call substitute(getline('.'), 'thepattern', '\=add(counter, submatch(0))[-1]', 'g')
" Note however that this flavour won't work with multi-lines patterns
" Now you can test the number of matches or do anything fancy with it
if len(matches) > 1
" replaces matches with nothing
:%s/thepattern//g
endif
Only if you want to define this as a function you'll need to play with:
exe 'normal :%s/'.escape(a:pattern, '/\').'/replacement..../flags....'

Passing line numbers from external command to run macro

I've seen a couple of questions about passing line numbers from Vim to an external command, but I want to do the opposite. I want to run a file through jshint and then apply corrections to each line number based on the jshint output.
For example, I'm trying to append a semicolon on each line that is missing one. Right now I'm shelling out to jshint and parsing the output but I'm not sure how I can use that to run a macro on multiple lines.
My current thought right now is to:
call jshint and parse out the line numbers for "Missing semicolon" errors
iterate through line numbers
for each line number, run G<LINE_NUMBER>A;
Here is what I have so far for parsing the jshint output:
:r ! jshint % | grep 'Missing semicolon' | awk '{ print $3 }' | sed 's/,//'
Is there a convenient way for me to do something like xargs in Vim or to parse the output of the external command into an array that I can loop over?
Well, let's see. You might try using errorformat:
let lines = split(system('jshint --verbose ' . shellescape(expand('%', 1))), "\n", 1)
let &errorformat = '%f: line %l\, col %v\, %m'
cgetexpr lines
for line in uniq(sort(map(filter(getqflist(), 'v:val["valid"] && v:val["text"] =~# "\\m^Missing semicolon"'), 'v:val["lnum"]')))
execute line . 's/$/;/'
endfor
Not what I'd call "convenient", but what do I know.
Then it might occur to you that the missing semicolons might not always be at end of lines. So you'd modify the code like this:
function! Cmp(a, b)
return a:a[0] == a:b[0] ? a:b[1] - a:a[1] : a:b[0] - a:a[0]
endfunction
let lines = split(system('jshint --verbose ' . shellescape(expand('%', 1))), "\n", 1)
let &errorformat = '%f: line %l\, col %v\, %m'
cgetexpr lines
for p in uniq(sort(map(filter(getqflist(), 'v:val["valid"] && v:val["text"] =~# "\\m^Missing semicolon"'),
\ '[str2nr(v:val["lnum"]), str2nr(v:val["col"])]'), 'Cmp'))
let line = getline(p[0])
call setline(p[0], line[ : p[1]-2] . ';' . line[p[1]-1 :])
endfor
Then it may occur to you that this doesn't handle the case of tabs. That's a problem because by default JSHint's idea of a tab is tab stop = 4, while Vim's is tab stop = 8. Then you... might fix that as an exercise, or you might come to your senses and use a real JavaScript parser to fix this instead of Vim. :)

In Vim, how to remove all lines that are duplicate somewhere

I have a file that contains lines as follows:
one one
one one
two two two
one one
three three
one one
three three
four
I want to remove all occurrences of the duplicate lines from the file and leave only the non-duplicate lines. So, in the example above, the result should be:
two two two
four
I saw this answer to a similar looking question. I tried to modify the ex one-liner as given below:
:syn clear Repeat | g/^\(.*\)\n\ze\%(.*\n\)*\1$/exe 'syn match Repeat "^' . escape(getline ('.'), '".\^$*[]') . '$"' | d
But it does not remove all occurrences of the duplicate lines, it removes only some occurrences.
How can I do this in vim? or specifically How can I do this with ex in vim?
To clarify, I am not looking for sort u.
If you have access to UNIX-style commands, you could do:
:%!sort | uniq -u
The -u option to the uniq command performs the task you require. From the uniq command's help text:
-u, --unique
only print unique lines
I should note however that this answer assumes that you don't mind that the output doesn't match any sort order that your input file might have already.
if you are on linux box with awk available, this line works for your needs:
:%!awk '{a[$0]++}END{for(x in a)if(a[x]==1)print x}'
Assuming you are on an UNIX derivative, the command below should do what you want:
:sort | %!uniq -u
uniq only works on sorted lines so we must sort them first with Vim's buit-in :sort command to save some typing (it works on the whole buffer by default so we don't need to pass it a range and it's a built-in command so we don't need the !).
Then we filter the whole buffer through uniq -u.
My PatternsOnText plugin version 1.30 now has a
:DeleteAllDuplicateLinesIgnoring
command. Without any arguments, it'll work as outlined in your question.
It does not preserve the order of the remaining lines, but this seems to work:
:sort|%s/^\(.*\)\n\%(\1\n\)\+//
(This version is #Peter Rincker's idea, with a little correction from me.) On vim 7.3, the following even shorter version works:
:sort | %s/^\(.*\n\)\1\+//
Unfortunately, due to differences between the regular-expression engines, this no longer works in vim 7.4 (including patches 1-52).
Taking the code from here and modifying it to delete the lines instead of highlighting them, you'll get this:
function! DeleteDuplicateLines() range
let lineCounts = {}
let lineNum = a:firstline
while lineNum <= a:lastline
let lineText = getline(lineNum)
if lineText != ""
if has_key(lineCounts, lineText)
execute lineNum . 'delete _'
if lineCounts[lineText] > 0
execute lineCounts[lineText] . 'delete _'
let lineCounts[lineText] = 0
let lineNum -= 1
endif
else
let lineCounts[lineText] = lineNum
let lineNum += 1
endif
else
let lineNum += 1
endif
endwhile
endfunction
command! -range=% DeleteDuplicateLines <line1>,<line2>call DeleteDuplicateLines()
This is not any simpler than #Ingo Karkat's answer, but it is a little more flexible. Like that answer, this leaves the remaining lines in the original order.
function! RepeatedLines(...)
let first = a:0 ? a:1 : 1
let last = (a:0 > 1) ? a:2 : line('$')
let lines = []
for line in range(first, last - 1)
if index(lines, line) != -1
continue
endif
let newlines = []
let text = escape(getline(line), '\')
execute 'silent' (line + 1) ',' last
\ 'g/\V' . text . '/call add(newlines, line("."))'
if !empty(newlines)
call add(lines, line)
call extend(lines, newlines)
endif
endfor
return sort(lines)
endfun
:for x in reverse(RepeatedLines()) | execute x 'd' | endfor
A few notes:
My function accepts arguments instead of handling a range. It defaults to the entire buffer.
This illustrates some of the functions for manipulating lists. :help list-functions
I use /\V (very no magic) so the only character I need to escape in a search pattern is the backslash itself. :help /\V
Add line number so that you can restore the order before sort
:%s/^/=printf("%d ", line("."))/g
sort
:sort /^\d+/
Remove duplicate lines
:%s/^(\d+ )(.*)\n(\d+ \2\n)+//g
Restore order
:sort
Remove line number added in #1
:%s/^\d+ //g
please use perl ,perl can do it easily !
use strict;use warnings;use diagnostics;
#read input file
open(File1,'<input.txt') or die "can not open file:$!\n";my #data1=<File1>;close(File1);
#save row and count number of row in hash
my %rownum;
foreach my $line1 (#data1)
{
if (exists($rownum{$line1}))
{
$rownum{$line1}++;
}
else
{
$rownum{$line1}=1;
}
}
#if number of row in hash =1 print it
open(File2,'>output.txt') or die "can not open file:$!\n";
foreach my $line1 (#data1)
{
if($rownum{$line1}==1)
{
print File2 $line1;
}
}
close(File2);

iterate through regex results vimscript

In vimscript, how can I iterate through all the matches of a regex in the current file and then run a shell command for each result?
I think this is a start but I cant figure out how to feed it the whole file and get each match.
while search(ENTIRE_FILE, ".*{{\zs.*\ze}}", 'nw') > 0
system(do something with THIS_MATCH)
endwhile
Assuming that we have a file with the content:
123 a shouldmatch
456 b shouldmatch
111 c notmatch
And we like to match
123 a shouldmatch
456 b shouldmatch
with the regex
.*shouldmatch
If you only have one match per line you can use readfile() and afterwards loop through the lines and check each line with matchstr(). [1]
function! Test001()
let file = readfile(expand("%:p")) " read current file
for line in file
let match = matchstr(line, '.*shouldmatch') " regex match
if(!empty(match))
echo match
" your command with match
endif
endfor
endfunction
You can put this function in your ~/.vimrc and call it with call Test001().
[1] http://vimdoc.sourceforge.net/htmldoc/eval.html#matchstr%28%29
You could use subtitute() instead. For instance...
call substitute(readfile(expand('%')), '.*{{\zs.*\ze}}',
\ '\=system("!dosomething ".submatch(1))', 'g')

How to insert spaces up to column X to line up things in columns?

I have my source code for copy operators written as follows.
foo = rhs.foo;
foobar = rhs.foobar;
bar = rhs.bar;
toto = rhs.toto;
I'd like to line things up as follows (more human readable, isn't it?).
foo = rhs.foo;
foobar = rhs.foobar;
bar = rhs.bar;
toto = rhs.toto;
Is there a VIM magic insert-up-to-column-N, or something like that that would allow me to line things up using a couple of keystrokes per line?
The other answers here are great, especially #nelstrom's comment for Tabular.vim and his excellent screencast.
But if I were feeling too lazy to install any Vim plugins, yet somehow willing to use Vim macros, I'd use macros.
The algorithm:
For each line,
Add tons of spaces before the symbol =
Go to the column you want to align to
Delete all text up to =, thereby shifting the = into the spot you want.
For your example,
foo = rhs.foo;
foobar = rhs.foobar;
bar = rhs.bar;
toto = rhs.toto;
Position the cursor anywhere on the first line and record the macro for that line by typing, in normal mode:
qa0f=100i <Esc>8|dwjq
Which translates to:
qa -- Record a macro in hotkey a
0 -- Go to the beginning of the line
f= -- Go to the first equals sign
100i <Esc> -- (There's a single space after the i, and the <Esc> means press escape, don't type "<Esc>".) Insert 100 spaces
8| -- Go to the 8th column (sorry, you'll have to manually figure out which column to align to)
dw -- Delete until the next non-space character
j -- Go to the next line
q -- Stop recording.
Then run the macro stored at hotkey a, 3 times (for the 3 remaining lines), by putting the cursor on the second line and pressing:
3#a
If you are using a unix-like environment, you can use the command line tool column. Mark your lines using visual mode, then:
:'<,'>!column -t
This pastes the selected text into the stdin of the command after '<,'>!. Note that '<,'>! is inserted automatically when you hit : in visual mode.
There is a nice plugin which does exactly that and more, called Align.vim
For you case, you would need to select your expression and then type :Align =. It will align everything, using = as a separator and reference.
(There is a lots of options to align, left, right, cyclically, etc)
You can also check Tabular.vim which provides similar features. See the screencast there for a demo.
A quick, simple way to proceed is to add X spaces and then delete back to column X. For example, if X=40, type
40a<Space><Esc>d40|
An alternative solution is to perform two consecutive substitutions:
%s/=/ =/
%s/\%>7c *//
The trick is the column pattern \%>7c that matches white spaces * only after the 7th column. Here foobar is the longest variable name with 6 characters so we need 7 in the regex.
We can use these two functions that I described in the below path for the same scenario : https://stackoverflow.com/a/32478708/3146151
simply put those two functions in your .vimrc or .gvimrc and call the functions as normal function call in your editor whenever you want.
The functions I have posted it here : https://github.com/imbichie/vim-vimrc-/blob/master/MCCB_MCCE.vim
We need to call this function in vim editor and give the Number of Occurrence of the Character or Space that you wants to move and the character inside the '' and the column number.
The number of occurrence can be from the starting of each line (MCCB function) or can be at the end of each line (MCCE function).
for the above example mentioned in the question we can use the MCCB function and the character we can use '=', so the usage will be like this in the vim editor.
:1,4call MCCB(1,'=',8)
So this will move the first = sign to the 8th column from line number 1 to 4.
These are the functions :
" MCCB - Move the Character to the Column from the Begin of line
" This is a function for Moving the specified Character
" in a given range of lines to a the specified Column from the Begin of the line
" NOTE 1 :- If the specified character and the first character of the line are same
" then the number of Occurance (num_occr) will be one less than the actual
" NOTE 2 :- Maximum space between the specified character with in the range
" of lines should be less than or equal to 80, if we need more than 80
" then we need to insert more spaces by increasing the value 80 in the
" "nmap s 80i <ESC>" line inside the function
" Usage :- in command mode do it like below
" Eg 1:- :5,11call MCCB(1, '=', 8)
" The above command will move the 1st Occurance from the begin of Character =
" to the 8th Column of the lines from 5 to 11
" Eg 2 :- :7,10call MCCB(2, '+', 12)
" The above command will move the 2nd Occurance of Character = to the 12th
" Column of the lines from 7 to 10
function! MCCB (num_occr, mv_char, col_num) range
if (a:firstline <= a:lastline)
nmap s 80i <ESC>
let line_num = a:firstline
while line_num <= a:lastline
execute "normal " . line_num . "G0" . a:num_occr . "f" . a:mv_char . "s" . a:col_num . "|dw"
let line_num = line_num + 1
endwhile
nunmap s
else
execute printf('ERROR : Start line %d is higher thatn End line %d, a:firstline, a:lastline)
endif
endfunction
" MCCE - Move the Character to the Column from the End of line
" This is a function for Moving the specified Character
" in a given range of lines to a the specified Column from the End of the line
" NOTE 1 :- If the specified character and the last character of the line are same
" then the number of Occurance (num_occr) will be one less than the actual
" NOTE 2 :- Maximum space between the specified character with in the range
" of lines should be less than or equal to 80, if we need more than 80
" then we need to insert more spaces by increasing the value 80 in the
" "nmap s 80i <ESC>" line inside the function
" Usage :- in command mode do it like below
" Eg 1:- :5,11call MCCE(1, ';', 20)
" The above command will move the 1st Occurance from the End of Character ;
" to the 20th Column of the lines from 5 to 11
" Eg 2 :- :7,10call MCCE(5, 'i', 26)
" The above command will move the 5th Occurance from the End of Character i
" to the 26th Column of the lines from 7 to 10
function! MCCE (num_occr, mv_char, col_num) range
if (a:firstline <= a:lastline)
nmap s 80i <ESC>
let line_num = a:firstline
while line_num <= a:lastline
execute "normal " . line_num . "G$" . a:num_occr . "F" . a:mv_char . "s" . a:col_num . "|dw"
let line_num = line_num + 1
endwhile
nunmap s
else
execute printf('ERROR : Start line %d is higher thatn End line %d, a:firstline, a:lastline)
endif
endfunction
I know this is old but I thought #talklittle had the right idea, the answer just got verbose. A quicker way is to insert spaces after the = and then remove all spaces after the 10th column like this:
:1,4 s/^\(.*=\) *\(.*\)$/\1 \2/
:1,4 s/^\(.\{10\}\) *\(.*\)$/\1\2/

Resources