5

私は vim ユーザーであり、置換するときに部分文字列の範囲をループできるようにしたいと考えています。次のような一連の行から移動するために、vim マジックを使用するにはどうすればよいですか。

Afoo
Bfoo
Cfoo
Dfoo

Abar
Bbar
Cbaz
Dbaz

?

ファイルを最初から検索して次の を検索し、最初の 2 つのインスタンスをに、次の2 つのfooインスタンスを に置き換えます。for ループを使用するのが最善の選択肢ですか? その場合、置換コマンドでループ変数を使用するにはどうすればよいですか?barbaz

4

5 に答える 5

11

状態を持つ関数を使用し、この関数を %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'])/

必要に応じて、2 つのコマンドの呼び出しを 1 つのコマンドにカプセル化することもできます。


編集:これは、次のコマンドとして統合されたバージョンです。

  • 必要な数の置換を受け入れます。すべての置換は、区切り文字で区切る必要があります。
  • 後方参照をサポートします。
  • 最初に出現した 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

コマンド

%RotateSubstitute/\(.\)foo\(.\)/\2bar\1/\1bar\2/

生成されます:

ZbarA
BbarE
RbarC
DbarT
于 2009-11-27T17:01:53.837 に答える
2

これは厳密にはあなたが望むものではありませんが、サイクルには役立ちます。

私はプラグイン swapit http://www.vim.org/scripts/script.php?script_id=2294を作成しました。これは、とりわけ、文字列のリストを循環させるのに役立ちます。例えば。

 :Swaplist foobar foo bar baz

次に入力します

 This line is a foo

単純なヤンク/ペースト行を作成し、最後の単語に移動して ctrl-a スワップします。

 qqyyp$^A

次に、スワップパターンを実行します

 100@q

取得するため

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} センシティブですが、おそらくあなたの問題に適用される可能性があります。

于 2009-11-28T00:26:51.333 に答える
2

なぜだめですか:

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

それが問題の幅をカバーしているかどうかはわかりませんが、単純な代替品であるという利点があります。これがカバーしない場合、より複雑なものがソリューションをカバーする可能性があります。

于 2009-12-12T19:20:25.873 に答える
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.
1000@a      Do a many times.

それをより良くする方法についてのアドバイスは大歓迎です。

ありがとう、マーティン。

于 2009-11-27T16:49:49.630 に答える
0

最初の 2 つを置き換えることができるマクロを記録し、残りの部分に :s を使用する方がおそらくはるかに簡単です。

マクロは次のようになり/foo^Mcwbar^[ます。マクロ モードに慣れていない場合はq、 , a(マクロを格納するレジスタ) を押してから、キーストロークを押してください/foo <Enter> cwbar <Escape>

マクロを取得したら2@a、現在のバッファ内の最初の 2 つのオカレンス:sを置き換え、残りを通常どおりに置き換えます。

于 2009-11-27T16:27:35.200 に答える