2009-11-27 71 views
5

我是vim的用户,我希望能够在代替时遍历一系列子字符串。我如何可以使用一些魔法的vim从这样一组线走:如何从VIM中的字符串列表中进行替换?

Afoo 
Bfoo 
Cfoo 
Dfoo 

Abar 
Bbar 
Cbaz 
Dbaz 

我想从下一次出现foo开始搜索我的文件,并用bar替换前两个实例,用baz替换前两个实例。使用for循环是最好的选择?如果是这样,那么我如何在替代命令中使用循环变量?

回答

9

我会使用一个具有状态的函数,并从%s调用此函数。喜欢的东西:

" 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 

而且随着使用它们:如果你希望

:call InitRotateSubst() 
:%s/foo/\=RotateSubst(['bar', 'bar', 'baz', 'baz'])/ 

的调用两个命令可以被封装成一个单一的命令。


编辑:这里是集成为一个命令一个版本:

  • 如我们所愿,所有的替代品需要与分隔字符分隔接受尽可能多的替代品;
  • 支持反向引用;
  • 只能更换N个第一事件,N ==如果命令调用撞指定替换的数目(与!)
  • 不支持通常的标志像G,I(:h :s_flags) - 为,例如,我们可能会将命令调用强加给/(或任何分隔符),否则最后的文本将被解释为标志。

下面是命令的定义:

: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 

这可以这样使用:

:%RotateSubstitute#foo#bar#bar#baz#baz# 

甚至,考虑到初始文本:

AfooZ 
BfooE 
CfooR 
DfooT 

命令

​​3210

会产生:

​​
0

它可能会要容易得多录制宏,可替换的前两个,然后用:S为休息。

宏可能看起来像/foo^Mcwbar^[。如果您对宏模式不熟悉,只需点击q,a(将其存储在该寄存器中),然后点击击键/foo <Enter> cwbar <Escape>

现在,一旦您获得了该宏,请执行[email protected]来替换当前缓冲区中的前两个事件,并通常使用:s来替换其他缓冲区。

1

这是我如何尝试这个宏。

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. 
[email protected]  Do a many times. 

任何关于如何做得更好的建议是值得欢迎的。

谢谢, Martin。

+0

看起来像是你在最后的''n'之前缺少'。' – Rich 2018-02-26 09:52:45

2

这并不是严格的,你想要什么,但能为周期是有用的。

我写了一个插件swapit http://www.vim.org/scripts/script.php?script_id=2294除其他东西都可以通过字符串列表与循环帮助。例如。

:Swaplist foobar foo bar baz 

然后键入

This line is a foo 

创建一个简单的抽出/贴线,去最后一个字和Ctrl-交换。

qqyyp$^A 

然后执行交换模式

[email protected] 

得到

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 
... 

它很可能被应用到你的问题虽然其{} cword敏感。

2

为什么不:

:%s/\(.\{-}\)foo\(\_.\{-}\)foo\(\_.\{-}\)foo\(\_.\{-}\)foo/\1bar\2bar\3baz\4baz/ 

我不知道它涵盖了问题的广度,但确实有凭借的是一个简单的替代品。如果这个问题没有解决,则更复杂的解决方案可能会涵盖解决方案

+0

嘿德里克, 只是想说我昨天在vimeo上观看了一些你的vim视频。我只想说我喜欢看他们,并且学习了一些东西。 如果我用正则表达式更好一些,你的解决方案就是我所能达到的。感谢这个简单的解决方案。 – Ksiresh 2009-12-18 13:12:48