문제
저는 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
일반적으로 나머지를 교체합니다.