I've often seen assignments to variables of the form "let s.='something'" Here's the specific piece of code in a vim script that I've been struggling to understand:
let s .= '%' . i . 'T'
let s .= (i == t ? '%1*' : '%2*')
let s .= ' '
let s .= i . ':'
let s .= winnr . '/' . tabpagewinnr(i,'$')
let s .= ' %*'
let s .= (i == t ? '%#TabLineSel#' : '%#TabLine#')
The code adds the tab number (i) and viewport number (winnr of tabpagewinnr(i,'$')) to the tab name, so that it looks something like "1: 2/4 Buffer name". From the looks of it, the .= operation seems to be appending stuff to s. But then, I don't understand what the first two lines do. Any help is appreciated.
vim's online help is your friend:
:h .=
:let {var} .= {expr1} Like ":let {var} = {var} . {expr1}".
:h expr-.
expr6 . expr6 .. String concatenation
:h expr1 (well - this is a little hard to find):
expr2 ? expr1 : expr1
The expression before the '?' is evaluated to a number. If it evaluates to TRUE, the result is the value of the expression between the '?' and ':', otherwise the result is the value of the expression after the ':'.
Example:
:echo lnum == 1 ? "top" : lnum
Since the first expression is an "expr2", it cannot contain another ?:. The
other two expressions can, thus allow for recursive use of ?:.
Example:
:echo lnum == 1 ? "top" : lnum == 1000 ? "last" : lnum
To keep this readable, using |line-continuation| is suggested:
:echo lnum == 1
:\ ? "top"
:\ : lnum == 1000
:\ ? "last"
:\ : lnum
You should always put a space before the ':', otherwise it can be mistaken for
use in a variable such as "a:1".
One at a time:
let s .= '%' . i . 'T'
Assuming i=9 and s="bleah", s will now be "bleah%9T"
let s .= (i == t ? '%1*' : '%2*')
This is the familiar ternary operator from C. If t==9, then s is now "bleah%9T%1*". If t is anything but 9, then s is now "bleah%9T%2*"
Related
I sometimes use the string() function in order to generate parsable strings.
For example, :echo string("hello world") shows:
'hello world'
But if I add real carriage returns into the string, the result becomes unparsable, simply because
the carriage returns are not transformed.
For example, :echo string("hello\nworld") shows:
'hello
world'
I would have expected:
"hello\nworld"
Is there a way to get a parsable version of any string, without writing a dedicated function?
EDIT
To be more precise, I need "parsable" strings to be used with the :execute command, in order to create customizable mappings. The basic idea is to be able to use the following code, even when there are some CRs in the a:toinsert argument:
function! InsertMapping(lhs, toinsert)
let l:rhs = printf('<c-r>=%s<cr>', string(a:toinsert))
exe 'inoremap' a:lhs l:rhs
endf
" This call is working:
call InsertMapping('<c-r><c-e>', "hello world")
" This one throws an error:
call InsertMapping('<c-r><c-e>', "hello\nworld")
" E492: Not an editor command: world')<cr>
Indeed, the last call to InsertMapping() will try to execute:
inoremap <c-r><c-e> <c-r>='hello
world'<cr>
Buf of course, I need to execute instead:
inoremap <c-r><c-e> <c-r>="hello\nworld"<cr>
Please note that I need to keep <c-r>= because my real use case is more complex, and needs some function calls; so the mapping can't be simplified like this:
inoremap <c-r><c-e> hello<cr>world
Don't know what do you exactly mean "parsable". If you want to use eval() to get the original string value, it is working. You see the real linebreak was printed out because of the echo() function, it expanded the \n as a linebreak.
Give this a try:
let a="hi\nworld"
echo a==eval(string(a))
echo a==eval(string("hi\nworld"))
both will return 1 (true). So it is "parsable".
If I didn't understand the meaning of your "parsable", please make some example.
I finally wrote a dedicated function to stringify a vim string; it's far from perfect, but it will give a "correct" output for all the tested cases. When really nothing at all is special inside the string, it returns a single-quoted string, which may slightly improve performance in some rare cases.
Here is the code:
let s:specials = {
\ "\b":'\b', "\e":'\e', "\f":'\f', "\n":'\n',
\ "\r":'\r', "\t":'\t', "\\":'\\', "\"":'\"',
\}
function! Stringify(source)
let output = ''
let string_is_special = v:false
for i in range(strlen(a:source))
let char = a:source[i]
let ascii = char2nr(char)
let char_is_special = v:false
for [key, str] in items(s:specials)
if char == key
let output .= str
let char_is_special = v:true
let string_is_special = v:true
break
endif
endfor
if !char_is_special
if ascii < 32
let output .= printf('\x%02x', ascii)
let string_is_special = v:true
else
let output .= char
endif
endif
endfor
return printf(string_is_special ? '"%s"' : "'%s'", output)
endf
Here are some quick tests:
let tests = [
\ "simple string",
\ 'back \ slash',
\ "carriage \n return",
\ "utf8 frénçh àccènts",
\ "\x47\x6e\x75",
\ "ctrl-w special key: \<c-w>",
\ ]
for s in tests
echo Stringify(s)
endfor
Here is the tests output:
'simple string'
"back \\ slash"
"carriage \n return"
'utf8 frénçh àccènts'
'Gnu'
"ctrl-w special key: \x17"
In Vim, single quotes and double quotes have different behaviors.
Between double quotes, special characters like \n are interpreted, so:
"hello\nworld"
becomes:
hello
world
and you would have to double the \ to actually get a \n:
"hello\\nworld"
Between single quotes, special characters are not interpreted, so:
'hello\nworld'
becomes:
hello\nworld
All of that is explained under :help expr-" and :help expr-'.
I have been poking around for a good solution for a vim thesaurus. The capability is built-in, obviously, but the file everyone seems to use is the mthesaur.txt. While it 'works' in the sense that the commands in insert mode bring up a list, it seems to me the results are programatically correct but not super useful. The vim online thesaurus plugin works very well, but the latency over the wire and necessity of using a split for the returned buffer is less than ideal. Anyone have an opinion about this?
I have written a plugin that can address the two issues you raised here.
Multi-language Thesaurus Query plugin for Vim
It improves the using experience in two regards: more sensible synonym choosing
mechanism; and better and more flexible synonym source(s).
Thesaurus_query.vim screen cast
By default, the plugin uses vim's messagebox for candidate display, with each
synonym labeled by a number. And it let user choose the suitable one to replace
the word under cursor by typing in its number. It works similar to vim's
default spell correction prompt. And drastically reduced the operation time for
choosing proper synonym from a long list of candidates.
To improve the quality of synonym candidates, multiple query backends were
used. For English user, two are note worthy.
thesaurus_com Backend using Thesaurus.com as synonym source
mthesaur_txt Backend using mthesaur.txt as synonym source
thesaurus_com Backend will work straight away. For Local Query Backend to work,
you will need to download mthesaur.txt and tell the plugin where it is
located either by setting variable thesaurus or specifying
variable g:tq_mthesaur_file. Or else only Online Backend will be
functional.
By default, Online Query Backend will be used first. But if internet is not
available or too slow, future query in the current vim session will be handled
by Local Query Backend first to reduce latency time. Priority of these two
backends can also be manually altered(see documentation).
To address the latency issue(which usually stands out when the word is not found), I have introduced a timeout mechanism. You may set
let g:tq_online_backends_timeout = 0.6
if your internet is reasonably fast. So that the latency could be reduced to
under 0.6 second.
The plugin is written in Python, though. So you might want to use it with Vim compiled with Python and/or Python3 support.
If your system is unix-like and if you have awk installed, then I have a
simple solution to your problem that gives you access to thesauri in
multiple languages without internet connection and without a split
window either.
First download LibreOffice thesauri from:
https://cgit.freedesktop.org/libreoffice/dictionaries/tree/
for example.
(Look after th_*.dat files, these are the ones you need, not the .aff and
.dic files which work only for spellchecking with Hunspell.) Download
the *.dat thesauri of your liking and copy them to a subdirectory of the
folder where you will put your plugin; this subdirectory should be
called, "thes."
Now create a new file in your plugin folder (the folder where you should
have the "thes" subdirectory with the *.dat thesauri inside) and put the
following in this file:
" offer choice among installed thesauri
" ==================================================
let s:thesaurusPath = expand("<sfile>:p:h") . "/thes"
function! s:PickThesaurus()
" 1, 1: glob does not ignore any pattern, returns a list
let thesaurusList = glob(s:thesaurusPath . "/*", 1, 1)
if len(thesaurusList) == 0
echo "Nothing found in " . s:thesaurusPath
return
endif
let index = 0
let optionList = []
for name in thesaurusList
let index = index + 1
let shortName = fnamemodify(name, ":t:r")
let optionList += [index . ". " . shortName]
endfor
let choice = inputlist(["Select thesaurus:"] + optionList)
let indexFromZero = choice - 1
if (indexFromZero >= 0) && (indexFromZero < len(thesaurusList))
let b:thesaurus = thesaurusList[indexFromZero]
endif
endfunction
command! Thesaurus call s:PickThesaurus()
This will allow you to pick the thesaurus of your choice by typing
:Thesaurus in Vim's command mode.
(Actually, if you plan to use only one thesaurus then you don't need any
of this; just assign the full name of your thesaurus file to the
buffer-local variable, b:thesaurus).
Finally, add the following to your plugin file:
" run awk on external thesaurus to find synonyms
" ==================================================
function! OmniComplete(findstart, base)
if ! exists("b:thesaurus")
return
endif
if a:findstart
" first, must find word
let line = getline('.')
let wordStart = col('.') - 1
" check backward, accepting only non-white space
while wordStart > 0 && line[wordStart - 1] =~ '\S'
let wordStart -= 1
endwhile
return wordStart
else
" a word with single quotes would produce a shell error
if match(a:base, "'") >= 0
return
endif
let searchPattern = '/^' . tolower(a:base) . '\|/'
" search pattern is single-quoted
let thesaurusMatch = system('awk'
\ . " '" . searchPattern . ' {printf "%s", NR ":" $0}' . "'"
\ . " '" . b:thesaurus . "'"
\)
if thesaurusMatch == ''
return
endif
" line info was returned by awk
let matchingLine = substitute(thesaurusMatch, ':.*$', '', '')
" entry count was in the thesaurus itself, right of |
let entryCount = substitute(thesaurusMatch, '^.*|', '', '')
let firstEntry = matchingLine + 1
let lastEntry = matchingLine + entryCount
let rawOutput = system('awk'
\ . " '" . ' NR == ' . firstEntry . ', NR == ' . lastEntry
\ . ' {printf "%s", $0}' . "'"
\ . " '" . b:thesaurus . "'"
\)
" remove dash tags if any
let rawOutput = substitute(rawOutput, '^-|', '', '')
let rawOutput = substitute(rawOutput, '-|', '|', 'g')
" remove grammatical tags if any
let rawOutput = substitute(rawOutput, '(.\{-})', '', 'g')
" clean spaces left by tag removal
let rawOutput = substitute(rawOutput, '^ *|', '', '')
let rawOutput = substitute(rawOutput, '| *|', '|', 'g')
let listing = split(rawOutput, '|')
return listing
endif
endfunction
" configure completion
" ==================================================
set omnifunc=OmniComplete
set completeopt=menuone
This will allow you to get the synonyms of any word you type in insert
mode. While still in insert mode, press Ctrl-X Ctrl-O (or any key
combination you mapped on omnicompletion) and a popup menu will show up
with the synonym list.
This solution is very crude as compared to Chong's powerful plugin (see above), but it is lightweight and works well enough for me. I use it with thesauri in four different languages.
Script for ~/.vimrc, it needs the file thesaurii.txt (merged dictionaries from https://github.com/moshahmed/vim/blob/master/thesaurus/thesaurii.txt) and perl.exe in path for searching for synonyms. Script tested on win7 and cygwin perl.
Calls aspell to do spell correction, if no synonyms are found. See https://stackoverflow.com/a/53825144/476175 on how to call this function on pressing [tab].
set thesaurus=thesaurii.txt
let s:thesaurus_pat = "thesaurii.txt"
set completeopt+=menuone
set omnifunc=MoshThesaurusOmniCompleter
function! MoshThesaurusOmniCompleter(findstart, base)
" == First call: find-space-backwards, see :help omnifunc
if a:findstart
let s:line = getline('.')
let s:wordStart = col('.') - 1
" Check backward, accepting only non-white space
while s:wordStart > 0 && s:line[s:wordStart - 1] =~ '\S'
let s:wordStart -= 1
endwhile
return s:wordStart
else
" == Second call: perl grep thesaurus for word_before_cursor, output: comma separated wordlist
" == Test: so % and altitude[press <C-x><C-o>]
let a:word_before_cursor = substitute(a:base,'\W','.','g')
let s:cmd='perl -ne ''chomp; '
\.'next if m/^[;#]/;'
\.'print qq/$_,/ if '
\.'/\b'.a:word_before_cursor.'\b/io; '' '
\.s:thesaurus_pat
" == To: Debug perl grep cmd, redir to file and echom till redir END.
" redir >> c:/tmp/vim.log
" echom s:cmd
let s:rawOutput = substitute(system(s:cmd), '\n\+$', '', '')
" echom s:rawOutput
let s:listing = split(s:rawOutput, ',')
" echom join(s:listing,',')
" redir END
if len(s:listing) > 0
return s:listing
endif
" Try spell correction with aspell: echo mispeltword | aspell -a
let s:cmd2 ='echo '.a:word_before_cursor
\.'|aspell -a'
\.'|perl -lne ''chomp; next unless s/^[&]\s.*?:\s*//; print '' '
let s:rawOutput2 = substitute(system(s:cmd2), '\n\+$', '', '')
let s:listing2 = split(s:rawOutput2, ',\s*')
if len(s:listing2) > 0
return s:listing2
endif
" Search dictionary without word delimiters.
let s:cmd3='perl -ne ''chomp; '
\.'next if m/^[;#]/;'
\.'print qq/$_,/ if '
\.'/'.a:word_before_cursor.'/io; '' '
\.&dictionary
let s:rawOutput3 = substitute(system(s:cmd3), '\n\+$', '', '')
let s:listing3 = split(s:rawOutput3, ',\s*')
if len(s:listing3) > 0
return s:listing3
endif
" Don't return empty list
return [a:word_before_cursor, '(no synonyms or spell correction)']
endif
endfunction
What is the proper way to concatenate several scalar values into one Perl string?
The following code is deliberately a series of statements for debugging reasons.
my $bill_record;
$bill_record = $acct_no . " |";
$bill_record = $bill_record . defined($w_ptWtrMtrRecRef->{"mtr_addr_no"}) ? $w_ptWtrMtrRecRef->{"mtr_addr_no"} : " " . " |" ;
$bill_record = $bill_record . defined($w_ptWtrMtrRecRef->{"mtr_addr_str"}) ? $w_ptWtrMtrRecRef->{"mtr_addr_str"} : " " . " |" ;
$bill_record = $bill_record . defined($w_ptWtrMtrRecRef->{"mtr_addr_apt"}) ? $w_ptWtrMtrRecRef->{"mtr_addr_apt"} : " " . " |" ;
$bill_record = $bill_record . $issue_date . " |";
The | character is serving as a delimiter. Each line will be '\n terminated.
After the last line $bill_record = $bill_record . $issue_date . " |";
This error appears:
Use of uninitialized value $bill_record in concatenation (.) or string at /home/ics/include/WsBillFunc.pm line 1022.
at /home/ics/include/WsBillFunc.pm line 1022
$issue_date is defined when assigned.
What could be causing $bill_record to become undefined, and what is the proper way to concatenate a bunch of scalar values into one string?
I don't know specifically why $bill_record is undefined. But if I understand what you're trying to do, you're running into a precedence problem: the ?: operator has lower precedence than concatenation ., so that
$bill_record = $bill_record . defined($w_ptWtrMtrRecRef->{"mtr_addr_no"}) ? $w_ptWtrMtrRecRef->{"mtr_addr_no"} : " " . " |" ;
is treated as
$bill_record = ($bill_record . defined($w_ptWtrMtrRecRef->{"mtr_addr_no"})) ? $w_ptWtrMtrRecRef->{"mtr_addr_no"} : (" " . " |") ;
which I suspect is not what you want. Try adding parentheses:
$bill_record = $bill_record . (defined($w_ptWtrMtrRecRef->{"mtr_addr_no"}) ? $w_ptWtrMtrRecRef->{"mtr_addr_no"} : " ") . " |" ;
(Or use .= as another commenter suggested.)
I'd probably do that in one statement with a join:
$bill_record = join ' |',
map( {
defined( $_ ) ? $_ : ' '
} #{ $w_ptWtrMtrRecRef }{ qw( mtr_addr_no mtr_addr_str mtr_addr_apt ) }
),
$issue_date,
'';
In the map I limit with parens because I only want to apply it to the hash slice. After that is the $issue_date and the empty string. That empty string gets the final | you have.
But, for your problem, it looks like you have a precedence problem. One way to see this is to ask Perl to compile then deparse your program to see what it thinks you wanted. The B::Deparse module does this and I use the -p argument to add extra parentheses.
Here's a cut down version of your original program with the added call to the deparser at the top (it's the B::Deparse module but the namespace is O:
#!/usr/bin/perl
use O qw(Deparse -p);
my $b;
$b = $acct_no . " |";
$b = $b . defined($w->{"no"}) ? $w->{"no"} : " " . " |" ;
$b = $b . defined($w->{"str"}) ? $w->{"str"} : " " . " |" ;
$b = $b . defined($w->{"apt"}) ? $w->{"apt"} : " " . " |" ;
$b = $b . $issue_date . " |";
It outputs:
my($b);
($b = ($acct_no . ' |'));
($b = (($b . defined($$w{'no'})) ? $$w{'no'} : ' |'));
($b = (($b . defined($$w{'str'})) ? $$w{'str'} : ' |'));
($b = (($b . defined($$w{'apt'})) ? $$w{'apt'} : ' |'));
($b = (($b . $issue_date) . ' |'));
The key part is the (($b . defined($$w{'no'})). The current value of $b is concatenated with the return value of defined, then the conditional operator (? :) is done. If the test value is true, it returns the first value in the conditional.
When it gets to mgr_apt_no, there are probably many records that don't have that value set. However, the combined value of the previous $b and $$w{'apt'} is defined because $b is not empty. Thus, it chooses the value of $$w{'apt'} to assign to $b. When it does the last line, $b is empty for the concatenation with $issue_date.
I have some parameters in a text file which goes as follows
parameter1 =5
parameter2=4
----------
---------
parameter(n-1) = 6
parameter(n)=11
My requirement is that the values of the parameters should sum upto 100 and there can be number of parameters. I was wondering if I could write a function in Vim , which could calculate the sum and display it somewhere?
I have no idea how to pass arguement to such a function,I was thinking it could somehow be done by block selecting the lines with parameter values.
Add following function to your vimrc file:
function! CustomSum()
let sum = 0
for l in range( 1, line('$') )
let fields = split( getline(l), '\s*=\s*' )
if ( len( fields ) != 2 || fields[1] =~? '\D' )
continue
endif
let sum = sum + fields[1]
endfor
return sum
endfunction
And run it:
:echo CustomSum()
That with your input data yields:
26
EDIT to add a range to previous function. Now accepts a range as input parameters named a:firstline and a:lastline. I increment them with 0 to convert them to integers, otherwise the range function complains. Last line echoes the result for debugging but would be better idea to handle the result in a return call (only uncomment it).
function! CustomSum() range
let sum = 0
for l in range( a:firstline + 0, a:lastline + 0 )
let fields = split( getline(l), '\s*=\s*' )
if ( len( fields ) != 2 || fields[1] =~? '\D' )
continue
endif
let sum = sum + fields[1]
endfor
echo sum
"" return sum
endfunction
Now you can do visually selection or normal ranges, like:
:'<,'>call CustomSum()
or
:1,5call CustomSum()
Both should work.
This can also be done with a (pseudo) one-liner as well:
:let s=0
:g/parameter\d\+\s*=\s*\d\+/let s+=matchstr(getline('.'), '\d\+\s*$')
:echo s
It you want a function you fall back to Birei's solution
EDIT: BTW, we can also echo it directly with:
echo eval(join(map(filter(getline(1,'$'), 'v:val =~ "=\\s*\\d"'), 'matchstr(v:val, "=\\s*\\zs\\d\\+")'), '+'))
or if you prefer a command:
command! -nargs=0 -range=% Sum echo eval(join(map(filter(getline(<line1>,<line2>), 'v:val =~ "=\\s*\\d"'), 'matchstr(v:val, "=\\s*\\zs\\d\\+")'), '+'))
I am a vim user, and I want to be able to loop over a range of substrings when I am substituting. How can I use some vim magic to go from a set of lines like this:
Afoo
Bfoo
Cfoo
Dfoo
to
Abar
Bbar
Cbaz
Dbaz
?
I want to search my file from the start for the next occurance of foo, and replace the first two instances with bar, and the second two with baz. Is using a for loop the best option? If so, then how do I use the loop variable in the substitution command?
I would use a function that has a state, and call this function from %s. Something like:
" untested code
function! InitRotateSubst()
let s:rs_idx = 0
endfunction
function! RotateSubst(list)
let res = a:list[s:rs_idx]
let s:rs_idx += 1
if s:rs_idx == len(a:list)
let s:rs_idx = 0
endif
return res
endfunction
And use them with:
:call InitRotateSubst()
:%s/foo/\=RotateSubst(['bar', 'bar', 'baz', 'baz'])/
The call to the two commands could be encapsulated into a single command if you wish.
EDIT: Here is a version integrated as a command that:
accepts as many replacements as we wish, all the replacements needs to be separated with the separator-character ;
supports back-references ;
can replace only the N first occurrences, N == the number of replacements specified if the command call is banged (with a !)
does not support usual flags like g, i (:h :s_flags) -- for that, we would have for instance to impose the command call to always ends up with a / (or whatever separator-character), if not the last text is interpreted as flags.
Here is the command definition:
:command! -bang -nargs=1 -range RotateSubstitute <line1>,<line2>call s:RotateSubstitute("<bang>", <f-args>)
function! s:RotateSubstitute(bang, repl_arg) range
let do_loop = a:bang != "!"
" echom "do_loop=".do_loop." -> ".a:bang
" reset internal state
let s:rs_idx = 0
" obtain the separator character
let sep = a:repl_arg[0]
" obtain all fields in the initial command
let fields = split(a:repl_arg, sep)
" prepare all the backreferences
let replacements = fields[1:]
let max_back_ref = 0
for r in replacements
let s = substitute(r, '.\{-}\(\\\d\+\)', '\1', 'g')
" echo "s->".s
let ls = split(s, '\\')
for d in ls
let br = matchstr(d, '\d\+')
" echo '##'.(br+0).'##'.type(0) ." ~~ " . type(br+0)
if !empty(br) && (0+br) > max_back_ref
let max_back_ref = br
endif
endfor
endfor
" echo "max back-ref=".max_back_ref
let sm = ''
for i in range(0, max_back_ref)
let sm .= ','. 'submatch('.i.')'
" call add(sm,)
endfor
" build the action to execute
let action = '\=s:DoRotateSubst('.do_loop.',' . string(replacements) . sm .')'
" prepare the :substitute command
let args = [fields[0], action ]
let cmd = a:firstline . ',' . a:lastline . 's' . sep . join(args, sep)
" echom cmd
" and run it
exe cmd
endfunction
function! s:DoRotateSubst(do_loop, list, replaced, ...)
" echom string(a:000)
if ! a:do_loop && s:rs_idx == len(a:list)
return a:replaced
else
let res0 = a:list[s:rs_idx]
let s:rs_idx += 1
if a:do_loop && s:rs_idx == len(a:list)
let s:rs_idx = 0
endif
let res = ''
while strlen(res0)
let ml = matchlist(res0, '\(.\{-}\)\(\\\d\+\)\(.*\)')
let res .= ml[1]
let ref = eval(substitute(ml[2], '\\\(\d\+\)', 'a:\1', ''))
let res .= ref
let res0 = ml[3]
endwhile
return res
endif
endfunction
which could be used this way:
:%RotateSubstitute#foo#bar#bar#baz#baz#
or even, considering the initial text:
AfooZ
BfooE
CfooR
DfooT
the command
%RotateSubstitute/\(.\)foo\(.\)/\2bar\1/\1bar\2/
would produce:
ZbarA
BbarE
RbarC
DbarT
This is Not strictly what you want but can be useful for cycles.
I've written a plugin swapit http://www.vim.org/scripts/script.php?script_id=2294 which among other things can help with cycling through lists of strings. Eg.
:Swaplist foobar foo bar baz
then type
This line is a foo
create a simple yank/paste line, go to last word and ctrl-a swap.
qqyyp$^A
then execute the swap pattern
100#q
to get
This line is foo
This line is bar
This line is baz
This line is foo
This line is bar
This line is baz
This line is foo
This line is bar
This line is baz
This line is foo
This line is bar
This line is baz
...
It could probably be applied to your problem although its {cword} sensitive.
Why not:
:%s/\(.\{-}\)foo\(\_.\{-}\)foo\(\_.\{-}\)foo\(\_.\{-}\)foo/\1bar\2bar\3baz\4baz/
I'm not sure that it covers the breadth of the problem but does have the virtue of being a simple substitute. A more complex one may cover the solution if this one doesn't.
This is how I'd attempt that macro.
qa Records macro in buffer a
/foo<CR> Search for the next instance of 'foo'
3s Change the next three characters
bar To the word bar
<Esc> Back to command mode.
n Get the next instance of foo
. Repeat last command
n Get the next instance of foo
3s Change next three letters
baz To the word bar
<Esc> Back to command mode.
. Repeat last command
q Stop recording.
1000#a Do a many times.
Any advice on how to do it better is welcome.
thanks,
Martin.
It's probably going to be much easier to record a macro that can replace the first two, and then use :s for the rest.
The macro might look like /foo^Mcwbar^[. If you're not familiar with macro mode, just hit q, a (the register to store it in) and then the keystrokes /foo <Enter> cwbar <Escape>.
Now once you've got that macro, do 2#a to replace the first two occurrences in the current buffer and use :s normally to replace the rest.