Pergunta

Eu sou um usuário do VIM e quero ser capaz de dar um loop sobre uma variedade de substringas quando estou substituindo. Como posso usar um pouco de magia do vim para ir de um conjunto de linhas como esta:

Afoo
Bfoo
Cfoo
Dfoo

para

Abar
Bbar
Cbaz
Dbaz

?

Eu quero pesquisar meu arquivo desde o início para a próxima ocorrência de foo, e substitua as duas primeiras instâncias por bar, e os dois segundos com baz. O uso de um loop é a melhor opção? Se sim, como uso a variável de loop no comando de substituição?

Foi útil?

Solução

Eu usaria uma função que possui um estado e chamaria essa função de %s. Algo como:

" 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

E use -os com:

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

A chamada para os dois comandos pode ser encapsulada em um único comando, se desejar.


EDIT: Aqui está uma versão integrada como um comando que:

  • Aceita quantas substituições que desejamos, todas as substituições precisam ser separadas com o caractere separador;
  • suporta referências de volta;
  • pode substituir apenas as primeiras ocorrências n, n == o número de substituições especificadas se a chamada de comando for batida (com A!)
  • não suporta bandeiras habituais como g, eu (:h :s_flags)-Para isso, teríamos, por exemplo, para impor a chamada de comando para sempre acaba com A / (ou qualquer qualquer outro personagem separador), se não o último texto, é interpretado como sinalizadores.

Aqui está a definição de comando:

: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

que poderia ser usado desta maneira:

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

ou até, considerando o texto inicial:

AfooZ
BfooE
CfooR
DfooT

o comando

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

produziria:

ZbarA
BbarE
RbarC
DbarT

Outras dicas

Isso não é estritamente o que você deseja, mas pode ser útil para ciclos.

Eu escrevi uma troca de plug -in http://www.vim.org/scripts/script.php?script_id=2294 que, entre outras coisas, podem ajudar no ciclismo através de listas de cordas. Por exemplo.

 :Swaplist foobar foo bar baz

então digite

 This line is a foo

Crie uma linha simples de Yank/Colar, vá para a última palavra e swap Ctrl-A.

 qqyyp$^A

Em seguida, execute o padrão de troca

 100@q

para obter

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

Provavelmente poderia ser aplicado ao seu problema, embora seja {cword} sensível.

Por que não:

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

Não tenho certeza se ele cobre a amplitude do problema, mas tem a virtude de ser um substituto simples. Um mais complexo pode cobrir a solução se esta não.

É assim que eu tentaria essa 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.

Qualquer conselho sobre como fazer melhor é bem -vindo.

Obrigado, Martin.

Provavelmente será muito mais fácil gravar uma macro que pode substituir os dois primeiros e depois usar: S para o resto.

A macro pode parecer /foo^Mcwbar^[. Se você não está familiarizado com o modo macro, basta acertar q, a (o registro para armazená -lo) e depois as teclas /foo <Enter> cwbar <Escape>.

Agora, quando você tiver essa macro, faça 2@a para substituir as duas primeiras ocorrências no buffer atual e usar :s normalmente para substituir o resto.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top