Pencarian global dan ganti di Vim, mulai dari posisi kursor dan membungkus

112

Ketika saya mencari dengan / perintah mode Normal:

/\vSEARCHTERM

Vim memulai pencarian dari posisi kursor dan berlanjut ke bawah, melingkari ke atas. Namun, ketika saya mencari dan mengganti menggunakan :substituteperintah:

:%s/\vBEFORE/AFTER/gc

Vim dimulai dari bagian atas file.

Apakah ada cara untuk membuat Vim melakukan pencarian dan penggantian mulai dari posisi kursor dan membungkus ke atas setelah mencapai akhir?

bajingan
sumber
2
\vpattern- Pola 'sangat ajaib': karakter non-alfanumerik diinterpretasikan sebagai simbol regex khusus (tidak perlu melarikan diri)
Carlos Araya
apa gunanya memulai dari posisi saat ini dan membungkus file daripada menggunakan hanya %? Saya tidak melihat penggunaan yang masuk akal untuk itu Anda akan melalui seluruh file.
tymik
@tymik Tujuannya adalah untuk mulai menelusuri dari kursor, dan menelusuri setiap hasil langkah demi langkah. Tapi aku sudah bertahun-tahun tidak menggunakan Vim.
Basteln

Jawaban:

50

1.  Tidak sulit untuk mencapai perilaku menggunakan substitusi dua langkah:

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

Pertama, perintah substitusi dijalankan untuk setiap baris mulai dari yang sekarang hingga akhir file:

,$s/BEFORE/AFTER/gc

Kemudian, :substituteperintah itu diulangi dengan pola pencarian, string pengganti, dan flag yang sama, menggunakan :& perintah (lihat :help :&):

1,''-&&

Namun, yang terakhir melakukan substitusi pada rentang baris dari baris pertama file ke baris di mana tanda konteks sebelumnya telah ditetapkan, minus satu. Karena :substituteperintah pertama menyimpan posisi kursor sebelum memulai penggantian yang sebenarnya, baris yang dialamatkan oleh  ''adalah baris yang ada sebelum perintah substitusi itu dijalankan. ( '' Alamat mengacu pada tanda ' palsu; lihat :help :rangedan :help ''untuk detailnya.)

Perhatikan bahwa perintah kedua (setelah | pemisah perintah — lihat :help :bar) tidak memerlukan perubahan apa pun saat pola atau bendera diubah pada yang pertama.

2.  Untuk menyimpan beberapa pengetikan, untuk memunculkan kerangka dari perintah substitusi di atas pada baris perintah, seseorang dapat mendefinisikan pemetaan mode-Normal, seperti:

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

Bagian belakang <c-b><right><right><right><right>diperlukan untuk memindahkan kursor ke awal baris perintah ( <c-b>) dan kemudian empat karakter ke kanan ( <right> × 4), sehingga meletakkannya di antara dua tanda garis miring pertama, siap bagi pengguna untuk mulai mengetik pola pencarian . Setelah pola yang diinginkan dan penggantinya siap, perintah yang dihasilkan dapat dijalankan dengan menekan Enter.

(Seseorang mungkin mempertimbangkan untuk memiliki //daripada ///dalam pemetaan di atas, jika seseorang lebih memilih untuk mengetik pola, kemudian ketik garis miring pemisah itu sendiri, diikuti dengan string pengganti, daripada menggunakan panah kanan untuk memindahkan kursor ke atas garis miring pemisah yang sudah ada mulai bagian pengganti.)

ib.
sumber
1
Satu-satunya masalah dengan solusi ini adalah bahwa opsi terakhir (l) dan keluar (q) tidak menghentikan perintah pengganti kedua agar tidak berjalan
Steve Vermeulen
@eventualEntropy Lihat solusi saya di bawah ini tentang meminta pers 'q' lainnya.
q335r49
2
Jangan lupa (seperti yang saya lakukan) untuk melepaskan diri dari pipa jika Anda meletakkan ini dalam pemetaan kunci.
jazzabeanie
11
Ya Tuhan, apa arti semua karakter yang berubah-ubah itu. Bagaimana Anda mempelajarinya?
rakun berbatu
2
@Tropilio: Kemudian Anda dapat mencoba sesuatu seperti ini: :noremap <leader>R :,$s///gc\|1,''-&&<c-b><right><right><right><right>. Ini menempatkan :,$s///gc|1,''-&&ke dalam baris perintah dengan kursor di antara dua tanda garis miring pertama. Setelah Anda mengetik pola dan penggantian yang diinginkan, Anda dapat menjalankan perintah yang dihasilkan dengan menekan Enter .
ib.
174

Anda sudah menggunakan range %, yang merupakan singkatan 1,$dari keseluruhan file. Untuk beralih dari baris saat ini ke akhir yang Anda gunakan .,$. Periode berarti baris saat ini dan $berarti baris terakhir. Jadi perintahnya adalah:

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

Tetapi garis .atau arus dapat diasumsikan karena itu dapat dihilangkan:

:,$s/\vBEFORE/AFTER/gc

Untuk bantuan lebih lanjut lihat

:h range
Peter Rincker
sumber
Terima kasih, saya sudah tahu tentang itu. Saya ingin pencarian & ganti untuk membungkus setelah mencapai akhir dokumen. Dengan:., $ S tidak melakukan itu, hanya mengatakan "Pola tidak ditemukan".
Basteln
7
Untuk "sepuluh baris berikutnya":10:s/pattern/replace/gc
sini
10
walaupun bukan yang dicari OP, ini sangat membantu saya, terima kasih!
Tobias J
51

%adalah jalan pintas untuk 1,$
(Vim help => :help :%sama dengan 1, $ (seluruh file).)

. adalah posisi kursor yang dapat Anda lakukan

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

Untuk mengganti dari awal dokumen sampai kursor

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

dll

Saya sangat menyarankan Anda membaca manual tentang range :help rangekarena hampir semua perintah bekerja dengan range.

mb14
sumber
Terima kasih, saya sudah tahu tentang itu. Saya ingin pencarian & ganti untuk membungkus setelah mencapai akhir dokumen. Dengan:., $ S tidak melakukan itu, hanya mengatakan "Pola tidak ditemukan".
Basteln
@asteln: ada kesalahan ketik pada kiriman // apakah Anda salin dan tempel?
lihat
Jadi Anda ingin mengganti SEMUANYA tetapi karena Anda ingin meminta konfirmasi, Anda ingin memulai dari posisi saat ini. Lakukan saja dua kali.
mb14
Anda dapat menggunakan n dan & sebagai gantinya.
mb14
hmm, saya rasa n dan & adalah solusi terbaik, meskipun tidak persis seperti yang seharusnya (dengan prompt ya / tidak di setiap pertandingan). Tapi pasti cukup bagus, terima kasih! :)
basteln
4

Saya AKHIRNYA menemukan solusi untuk fakta bahwa berhenti dari pencarian membungkus awal file tanpa menulis fungsi yang sangat besar ...

Anda tidak akan percaya berapa lama saya harus memikirkan ini. Cukup tambahkan prompt apakah akan membungkus: jika pengguna menekan qlagi, jangan membungkus. Jadi pada dasarnya, hentikan penelusuran dengan mengetuk qqalih-alih q! (Dan jika Anda ingin membungkus, cukup ketik y.)

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

Saya sebenarnya telah memetakan ini ke hotkey. Jadi, misalnya, jika Anda ingin mencari dan mengganti setiap kata di bawah kursor, mulai dari posisi saat ini, dengan 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)
q335r49.dll
sumber
Menurut pendapat saya, pendekatan ini — memasukkan prompt tambahan di antara dua perintah yang diusulkan dalam jawaban saya — menambah kerumitan yang tidak perlu tanpa meningkatkan kegunaan. Dalam versi aslinya , jika Anda keluar dari perintah substitusi pertama (dengan q, latau Esc ) dan tidak ingin melanjutkan dari atas, Anda sudah dapat menekan qatau Esc lagi untuk segera keluar dari perintah kedua. Artinya, penambahan prompt yang diusulkan tampaknya secara efektif menduplikasi fungsionalitas yang sudah ada.
ib.
Bung ... apakah Anda MENCOBA pendekatan Anda? Katakanlah saya ingin mengganti 3 kemunculan berikutnya dari "let" dengan "unlet", dan kemudian - ini kuncinya - lanjutkan mengedit paragraf . Pendekatan Anda "Tekan y, y, y, sekarang tekan q. Sekarang Anda mulai mencari di baris pertama paragraf. Tekan q lagi untuk keluar. Sekarang kembali ke tempat Anda sebelumnya, untuk melanjutkan pengeditan. Oke, g ;. Jadi, pada dasarnya: yyyqq ... menjadi bingung ... g; (atau u ^ R) Metode saya: yyyqq
q335r49
Metode yang ideal, tentu saja yyyq, melanjutkan pengeditan, tanpa lompatan yang membingungkan. Tapi yyyqqhampir sama baiknya, jika Anda menganggap ketukan dua kali cukup banyak satu ketukan dalam hal kemudahan penggunaan.
q335r49
Baik perintah asli maupun versinya dengan prompt tidak mengembalikan kursor ke posisi sebelumnya dalam skenario itu. Yang pertama meninggalkan pengguna pada kemunculan pertama pola dari atas (di mana itu pada saat menekan quntuk kedua kalinya). Yang terakhir meninggalkan kursor pada kemunculan terakhir yang diganti (tepat sebelum yang pertama qditekan).
ib.
1
Dalam versi asli , jika lebih disukai untuk secara otomatis kembali kursor ke posisi sebelum nya (tanpa pengguna mengetik Ctrl + O ), salah satu bisa hanya menambahkan norm!``perintah: :,$s/BEFORE/AFTER/gc|1,''-&&|norm!``.
ib.
3

Berikut adalah sesuatu yang sangat kasar yang membahas kekhawatiran tentang membungkus pencarian dengan pendekatan dua langkah ( :,$s/BEFORE/AFTER/gc|1,''-&&) atau dengan perantara "Lanjutkan di awal file?" pendekatan:

" 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

Fungsi ini menggunakan beberapa peretasan untuk memeriksa apakah kita harus menyelesaikannya di atas:

  • Tidak ada pembungkusan jika pengguna menekan "l", "q" atau "Esc", yang mana saja menunjukkan keinginan untuk membatalkan.
  • Mendeteksi penekanan tombol terakhir dengan merekam makro ke dalam register "s" dan memeriksa karakter terakhirnya.
  • Hindari menimpa makro yang ada dengan menyimpan / memulihkan register "s".
  • Jika Anda sudah merekam makro saat menggunakan perintah, semua taruhan dibatalkan.
  • Mencoba melakukan hal yang benar dengan interupsi.
  • Hindari peringatan "rentang mundur" dengan penjagaan eksplisit.
meringis
sumber
1
Saya mengekstrak ini menjadi plug-in kecil dan meletakkannya di GitHub di sini .
meringis
Baru saja menginstal plugin Anda, itu berfungsi seperti pesona !! Terima kasih banyak telah membuat ini!
Tropilio
1

Saya terlambat ke pesta, tetapi saya terlalu sering mengandalkan jawaban stackoverflow yang terlambat untuk tidak melakukannya. Saya mengumpulkan petunjuk tentang reddit dan stackoverflow, dan opsi terbaik adalah menggunakan \%>...cpola dalam pencarian, yang hanya cocok setelah kursor Anda.

Meskipun demikian, itu juga mengacaukan pola untuk langkah penggantian berikutnya, dan sulit untuk diketik. Untuk mengatasi efek tersebut, fungsi kustom harus memfilter pola pencarian setelahnya, sehingga menyetel ulang. Lihat di bawah.

Saya telah bersaing dengan diri saya sendiri dengan pemetaan yang menggantikan kemunculan berikutnya dan melompat ke kejadian berikutnya setelahnya, dan tidak lebih (bagaimanapun juga tujuan saya). Saya yakin, dengan membangun ini, substitusi global dapat dilakukan. Perlu diingat saat mengerjakan solusi yang bertujuan pada sesuatu seperti :%s/.../.../gitu, pola di bawah menyaring kecocokan di semua baris yang tersisa ke posisi kursor - tetapi dibersihkan setelah substitusi tunggal selesai, jadi efek itu langsung hilang setelahnya, lompat ke pertandingan berikutnya dan dengan demikian dapat menjalankan semua pertandingan satu per satu.

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

simlei
sumber
Terima kasih, saya setuju bahwa jawaban yang terlambat sering kali membantu. Saya pribadi tidak menggunakan Vim lagi sejak lama (untuk alasan seperti ini), tetapi saya yakin banyak orang akan menganggap ini berguna.
Basteln