İmleç konumundan başlayarak ve etrafını sararak Vim'de global arama ve değiştirme


112

/ Normal mod komutuyla arama yaptığımda :

/\vSEARCHTERM

Vim, aramayı imleç konumundan başlatır ve aşağıya, etrafını sararak devam eder. Ancak, şu :substitutekomutu kullanarak arayıp değiştirdiğimde :

:%s/\vBEFORE/AFTER/gc

Vim bunun yerine dosyanın en üstünden başlar.

Vim arama ve imleç pozisyonundan başlayarak yerini gerçekleştirmek yapmak için bir yolu var mı ve bunun sonuna ulaştığında üstüne etrafında sarma?


2
\vpattern- 'çok sihirli' kalıp: alfanümerik olmayan karakterler özel normal ifade sembolleri olarak yorumlanır (kaçış gerekmez)
Carlos Araya

Geçerli konumdan başlayıp sadece kullanmak yerine dosyanın etrafını sarmanın amacı %nedir? Zaten tüm dosyayı gözden geçirdiğiniz için makul bir kullanım göremiyorum.
tymik

@tymik Amaç, imleçten aramaya başlamak ve her sonucu adım adım gözden geçirmekti. Ama Vim'i yıllardır kullanmadım.
basteln

Yanıtlar:


50

1.  İki aşamalı bir ikame kullanarak davranışı elde etmek zor değildir:

:,$s/BEFORE/AFTER/gc|1,''-&&

İlk olarak, ikame komutu mevcut olandan başlayarak dosyanın sonuna kadar her satır için çalıştırılır:

,$s/BEFORE/AFTER/gc

Daha sonra, bu :substitutekomut aynı arama düzeni, değiştirme dizesi ve bayraklarla, şu :& komutu kullanarak tekrarlanır (bkz. :help :&):

1,''-&&

Bununla birlikte, ikincisi, dosyanın ilk satırından önceki bağlam işaretinin ayarlandığı satıra eksi bir olan satır aralığı üzerinde ikame gerçekleştirir. İlk :substitutekomut, gerçek değiştirmelere başlamadan önce imleç konumunu sakladığından, adreslenen  ''satır, ikame komutu çalıştırılmadan önceki mevcut satırdır. ( '' Adres ' sözde işareti ifade eder ; ayrıntılar için bkz. :help :rangeVe :help ''.)

İkinci komutun ( | komut ayırıcısından sonra - bkz. :help :bar) İlkinde desen veya bayraklar değiştirildiğinde herhangi bir değişiklik gerektirmediğine dikkat edin.

2.  Bir miktar yazmayı kaydetmek için, yukarıdaki ikame komutunun iskeletini komut satırına getirmek için, bir Normal mod eşlemesi şu şekilde tanımlanabilir:

:noremap <leader>cs :,$s///gc\|1,''-&&<c-b><right><right><right><right>

Son <c-b><right><right><right><right>kısım, imleci komut satırının başına ( <c-b>) ve ardından dört karakter sağa ( <right> × 4) hareket ettirmek için gereklidir , böylece ilk iki eğik çizgi arasına, kullanıcının arama modelini yazmaya başlaması için hazırdır. . İstenilen model ve değiştirme hazır olduğunda, elde edilen komut tuşuna basılarak çalıştırılabilir Enter.

(Bir yaptırılır //yerine ///bir modeli yazın tercih ederse o zaman zaten mevcut ayırma çizgi başlayan imleci hareket ettirmek için yerine sağ oku kullanarak, yedek dizesi ardından ayırmayı çizgi kendini, yazın, yukarıda eşlemenizde yedek parça.)


1
Bu çözümle ilgili tek sorun, son (l) ve çıkış (q) seçeneklerinin ikinci ikame komutunun çalışmasını durdurmamasıdır
Steve Vermeulen

@eventualEntropy Başka bir 'q' basışını sormak için aşağıdaki çözümüme bakın.
q335r49

2
Bunu bir anahtar eşleştirmeye koyuyorsanız (benim yaptığım gibi) borudan kaçmayı unutmayın.
jazzabeanie

11
Aman tanrım, tüm bu keyfi karakterler ne anlama geliyor? Bunu nasıl öğrendin
rocky rakun

2
@Tropilio: O zaman böyle bir şey deneyebilirsiniz: :noremap <leader>R :,$s///gc\|1,''-&&<c-b><right><right><right><right>. :,$s///gc|1,''-&&İmleç ilk iki eğik çizgi işareti arasında olacak şekilde komut satırına koyar . İstenen kalıbı ve değiştirmeyi yazdıktan sonra, elde edilen komutu Enter tuşuna basarak çalıştırabilirsiniz .
ib.

174

Halihazırda bir aralık kullanıyorsunuz %, bu da 1,$dosyanın tamamını ifade etmek için kısa bir eldir . Mevcut satırdan kullandığınız sona gitmek için .,$. Dönem, mevcut satır $anlamına gelir ve son satır anlamına gelir. Yani komut şöyle olacaktır:

:.,$s/\vBEFORE/AFTER/gc

Ancak .veya mevcut satır varsayılabilir, bu nedenle kaldırılabilir:

:,$s/\vBEFORE/AFTER/gc

Daha fazla yardım için bkz.

:h range

Teşekkürler, bunu zaten biliyordum. Ara ve değiştir işlevinin belgenin sonuna ulaştığında etrafından dolaşmasını istiyorum. :., $ S ile bunu yapmaz, sadece "Kalıp bulunamadı" der.
basteln

7
"Sonraki on satır" için:10:s/pattern/replace/gc
burada

10
OP'nin aradığı bu olmasa da, bu bana çok yardımcı oldu, teşekkürler!
Tobias J

51

%1,$
(Vim yardımı => :help :%1'e eşit, $ (tüm dosya) için bir kısayol .)

. imleç konumu, böylece yapabilirsiniz

:.,$s/\vBEFORE/AFTER/gc

Belgenin başından imlece kadar değiştirmek için

:1,.s/\vBEFORE/AFTER/gc

vb

:help rangeHemen hemen tüm komutlar bir aralıkla çalıştığından, aralık hakkındaki kılavuzu okumanızı şiddetle tavsiye ederim .


Teşekkürler, bunu zaten biliyordum. Ara ve değiştir işlevinin belgenin sonuna ulaştığında etrafından dolaşmasını istiyorum. :., $ S ile bunu yapmaz, sadece "Kalıp bulunamadı" der.
basteln

@basteln: Gönderide yazım hatası var // kopyalayıp yapıştırdınız mı?
sehe

Yani HER ŞEYİ değiştirmek istiyorsunuz, ancak onay isterken mevcut konumdan başlamak istiyorsunuz. O zaman sadece iki kez yap.
mb14

bunun yerine n ve & kullanabilirsiniz.
mb14

hmm, sanırım n ve & en iyi çözüm, ancak tam olarak nasıl olması gerektiği bu değil (her maçta evet / hayır seçeneğiyle). Ama kesinlikle yeterince iyi, teşekkürler! :)
basteln

4

SONUNDA, aramayı bırakmanın muazzam bir işlev yazmadan dosyanın başlangıcına sarılması gerçeğine bir çözüm buldum ...

Bunu bulmamın ne kadar sürdüğüne inanamazsın. Yalnızca sarılıp sarmalanmayacağını soran bir mesaj ekleyin: kullanıcı qtekrar basarsa sarmayın. Yani temelde, qqyerine dokunarak aramadan çıkın q! (Kaydırmak istiyorsanız, yazmanız yeterlidir y.)

:,$s/BEFORE/AFTER/gce|echo 'Continue at beginning of file? (y/q)'|if getchar()!=113|1,''-&&|en

Aslında bunu bir kısayol tuşuyla eşleştirdim. Bu nedenle, örneğin, imlecin altındaki her kelimeyi şu anki konumdan başlayarak aramak ve değiştirmek istiyorsanız q*:

exe 'nno q* :,$s/\<<c-r>=expand("<cword>")<cr>\>//gce\|echo "Continue at beginning of file? (y/q)"\|if getchar()==121\|1,''''-&&\|en'.repeat('<left>',77)

Bence, önerilen iki komutların arasındaki bu yaklaşım-ekleme ek bilgi isteminde Cevabıma kullanılabilirliğini artırarak olmadan gereksiz karmaşıklığı -yıl ekler. Gelen orijinal sürümüne sen (ilk ikame komutu çıkmak durumunda q, lya da Esc ) ve üstten devam etmek istemiyoruz, zaten basabilirsiniz qveya Esc hemen tekrar ikinci komutu çıkmak için. Yani, komut isteminin önerilen eklenmesi, halihazırda mevcut olan işlevselliği etkili bir şekilde kopyalıyor gibi görünmektedir.
ib.

Dostum ... yaklaşımını denedin mi? Diyelim ki "let" in sonraki 3 oluşumunu "unlet" ile değiştirmek istiyorum ve sonra - bu anahtar - paragrafı düzenlemeye devam edin . Yaklaşımınız "y, y, y'ye basın, şimdi q'ya basın. Şimdi paragrafın ilk satırında aramaya başlayın. Çıkmak için tekrar q tuşuna basın. Şimdi düzenlemeye devam etmek için daha önce bulunduğunuz yere geri dönün. Tamam, g ;. Yani, temelde: yyyqq ... kafası karışır ... g; (veya u ^ R) Metodum: yyyqq
q335r49

İdeal yöntem, elbette yyyq, kafa karıştırıcı sıçramalar olmadan düzenlemeye devam etmektir . Ancak yyyqq, iki kez dokunmayı kullanım kolaylığı açısından hemen hemen tek bir dokunuş olarak düşünürseniz, neredeyse aynı derecede iyidir.
q335r49

Ne orijinal komut ne de istemli versiyonu, imleci bu senaryodaki önceki konumuna geri döndürmez. İlki, kullanıcıyı, desenin ilk geçtiği yerde üstten ( qikinci kez basıldığı anda olduğu yerde) terk eder . İkincisi, imleci değiştirilen son yerde (ilk qbasılmadan hemen önce ) bırakır .
ib.

1
Gelen orijinal sürümüne otomatik (kullanıcı yazmadan onun öncesinde imleci dönmek için tercih edilir ise, Ctrl + O ), bir sadece ekleyebilirsiniz norm!``komutu: :,$s/BEFORE/AFTER/gc|1,''-&&|norm!``.
ib.

3

Burada, aramayı iki adımlı yaklaşımla ( :,$s/BEFORE/AFTER/gc|1,''-&&) veya ara bir "Dosyanın başlangıcında devam et?" İle sarmalama konusundaki endişeleri gideren çok kaba bir şey var. yaklaşmak:

" Define a mapping that calls a command.
nnoremap <Leader>e :Substitute/\v<<C-R>=expand('<cword>')<CR>>//<Left>

" And that command calls a script-local function.
command! -nargs=1 Substitute call s:Substitute(<q-args>)

function! s:Substitute(patterns)
  if getregtype('s') != ''
    let l:register=getreg('s')
  endif
  normal! qs
  redir => l:replacements
  try
    execute ',$s' . a:patterns . 'gce#'
  catch /^Vim:Interrupt$/
    return
  finally
    normal! q
    let l:transcript=getreg('s')
    if exists('l:register')
      call setreg('s', l:register)
    endif
  endtry
  redir END
  if len(l:replacements) > 0
    " At least one instance of pattern was found.
    let l:last=strpart(l:transcript, len(l:transcript) - 1)
    " Note: type the literal <Esc> (^[) here with <C-v><Esc>:
    if l:last ==# 'l' || l:last ==# 'q' || l:last ==# '^['
      " User bailed.
      return
    endif
  endif

  " Loop around to top of file and continue.
  " Avoid unwanted "Backwards range given, OK to swap (y/n)?" messages.
  if line("''") > 1
    1,''-&&"
  endif
endfunction

Bu işlev, en üste sarmamız gerekip gerekmediğini kontrol etmek için birkaç hile kullanır:

  • Kullanıcı iptal etme isteğini belirten "l", "q" veya "Esc" tuşuna basarsa sarma olmaz.
  • "S" yazmacına bir makro kaydederek ve son karakterini inceleyerek son tuşa basıldığını tespit edin.
  • "S" kaydını kaydederek / geri yükleyerek mevcut bir makronun üzerine yazmaktan kaçının.
  • Komutu kullanırken zaten bir makro kaydediyorsanız, tüm bahisler kapalıdır.
  • Kesintilerle doğru olanı yapmaya çalışır.
  • Açık bir koruma ile "geriye doğru menzil" uyarılarından kaçınır.

1
Bunu küçük bir eklentiye çıkardım ve burada GitHub'a koydum .
wincent

Eklentinizi yeni yüklediniz, cazibe gibi çalışıyor !! Bunu yaptığınız için çok teşekkür ederim!
Tropilio

1

Partiye geç kaldım, ancak bunu yapmamak için bu kadar geç stackoverflow yanıtlarına çok sık güveniyordum. Reddit ve stackoverflow hakkında ipuçları topladım ve en iyi seçenek, \%>...caramada yalnızca imlecinizden sonra eşleşen kalıbı kullanmaktır .

Bununla birlikte, bir sonraki değiştirme adımı için kalıbı da bozar ve yazması zordur. Bu etkilere karşı koymak için, özel bir işlev, daha sonra arama modelini filtrelemeli, böylece onu sıfırlamalıdır. Aşağıya bakınız.

Kendimi, bir sonraki oluşumun yerini alan ve sonrasına atlayan bir haritalama ile savundum, daha fazlası değil (zaten hedefimdi). Eminim, bundan yola çıkarak, küresel bir ikame uygulanabilir. :%s/.../.../gAşağıdaki modelin imleç konumunda kalan tüm satırlardaki eşleşmeleri filtreleyerek dışarıda bırakmasını hedefleyen bir çözüm üzerinde çalışırken aklınızda bulundurun - ancak tek değiştirme tamamlandıktan sonra temizlenir, böylece bu etkiyi hemen kaybeder, bir sonraki maç ve böylece tüm maçları tek tek geçebilir.

fun! g:CleanColFromPattern(prevPattern) return substitute(a:prevPattern, '\V\^\\%>\[0-9]\+c', '', '') endf nmap <F3>n m`:s/\%><C-r>=col(".")-1<CR>c<C-.r>=g:CleanColFromPattern(getreg("/"))<CR>/~/&<CR>:call setreg("/", g:CleanColFromPattern(getreg("/")))<CR>``n


Teşekkürler, geç yanıtın çoğu zaman yardımcı olduğuna katılıyorum. Ben kişisel olarak artık Vim'i uzun zamandır kullanmıyorum (bunun gibi nedenlerle), ama eminim birçok insan bunu faydalı bulacaktır.
basteln
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.