문제

저는 VIM 사용자이며 대체 할 때 다양한 하위 문자열을 반복 할 수 있기를 원합니다. VIM 마법을 사용하여 다음과 같은 라인 세트에서 어떻게 이동할 수 있습니까?

Afoo
Bfoo
Cfoo
Dfoo

에게

Abar
Bbar
Cbaz
Dbaz

?

다음에 발생하기 위해 처음부터 내 파일을 검색하고 싶습니다. foo, 처음 두 인스턴스를 교체하십시오 bar, 두 번째 두 개 baz. for loop을 사용하는 것이 가장 좋은 옵션입니까? 그렇다면 대체 명령에서 루프 변수를 어떻게 사용합니까?

도움이 되었습니까?

해결책

상태가있는 함수를 사용하고 %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 == 명령 호출이 두드려있을 때 지정된 교체 수는 (a!)
  • 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

다른 팁

이것은 당신이 원하는 것이 아니라 사이클에 유용 할 수 있습니다.

플러그인 스왑을 작성했습니다 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}에 민감하지만 문제에 적용될 수 있습니다.

왜 안 돼:

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

나는 그것이 문제의 폭을 다루는 것을 확신하지 못하지만 단순한 대체물이라는 미덕이 있습니다. 더 복잡한 솔루션은 솔루션을 다룰 수 있습니다.

이것이 내가 그 매크로를 시도하는 방법입니다.

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.

더 나은 방법에 대한 조언은 환영합니다.

고마워요, 마틴.

아마도 처음 두 개를 대체 할 수있는 매크로를 기록하고 나머지는 S를 사용하는 것이 훨씬 쉬울 것입니다.

매크로는 모양이 될 수 있습니다 /foo^Mcwbar^[. 매크로 모드에 익숙하지 않다면 그냥 누르십시오. q, a (저장하는 레지스터) 및 키 스트로크 /foo <Enter> cwbar <Escape>.

이제 매크로를 얻으면 그렇습니다 2@a 현재 버퍼 및 사용에서 처음 두 가지 발생을 교체하려면 :s 일반적으로 나머지를 교체합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top