كيف يمكنني الاستبدال من قائمة الأوتار في VIM؟
-
05-07-2019 - |
سؤال
أنا مستخدم VIM ، وأريد أن أكون قادرًا على الحلقة على مجموعة من السلاسل الفرعية عندما أستبدل. كيف يمكنني استخدام بعض سحر Vim للانتقال من مجموعة من الخطوط مثل هذا:
Afoo
Bfoo
Cfoo
Dfoo
إلى
Abar
Bbar
Cbaz
Dbaz
?
أريد البحث عن ملفي من البداية عن الحدوث التالي foo
, واستبدل أول حالتين bar
, والثاني مع baz
. هل يستخدم خيار الحلقة الأفضل؟ إذا كان الأمر كذلك ، فكيف يمكنني استخدام متغير الحلقة في أمر الاستبدال؟
المحلول
أود استخدام وظيفة لها حالة ، وأطلق عليها هذه الوظيفة من ٪ 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 == عدد البدائل المحددة إذا كانت مكالمة الأوامر مخطوة (مع!)
- لا يدعم الأعلام المعتادة مثل G ، أنا (
:h :s_flags
)-لذلك ، سيكون علينا على سبيل المثال فرض دعوة الأوامر أن ينتهي دائمًا بـ A / (أو أي شيء فاصل) ، إذا لم يكن النص الأخير يتم تفسيره على أنه أعلام.
هنا هو تعريف الأمر:
: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
قم بإنشاء خط yank/لصق بسيط ، انتقل إلى آخر كلمة ومبادلة 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
عادة لاستبدال الباقي.