How do I substitute from a list of strings in VIM?

后端 未结 5 1515
感情败类
感情败类 2020-12-21 00:26

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:



        
相关标签:
5条回答
  • 2020-12-21 00:50

    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.

    0 讨论(0)
  • 2020-12-21 00:57

    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.

    0 讨论(0)
  • 2020-12-21 01:03

    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
    
    0 讨论(0)
  • 2020-12-21 01:05

    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.

    0 讨论(0)
  • 2020-12-21 01:08

    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.

    0 讨论(0)
提交回复
热议问题