2009-11-27 72 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