كيف يمكنني الاستبدال من قائمة الأوتار في VIM؟

StackOverflow https://stackoverflow.com/questions/1809571

  •  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 عادة لاستبدال الباقي.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top