Tìm kiếm toàn cục và thay thế trong Vim, bắt đầu từ vị trí con trỏ và bao quanh


112

Khi tôi tìm kiếm bằng / lệnh Chế độ bình thường:

/\vSEARCHTERM

Vim bắt đầu tìm kiếm từ vị trí con trỏ và tiếp tục đi xuống, bao quanh đầu. Tuy nhiên, khi tôi tìm kiếm và thay thế bằng :substitutelệnh:

:%s/\vBEFORE/AFTER/gc

Thay vào đó, Vim bắt đầu ở đầu tệp.

Có cách nào để Vim thực hiện tìm kiếm và thay thế bắt đầu từ vị trí con trỏ quấn quanh đầu khi nó đến cuối không?


2
\vpattern- mẫu 'rất kỳ diệu': ký tự không phải chữ và số được hiểu như là những biểu tượng regex đặc biệt (không thoát cần thiết)
Carlos Araya

Đâu là điểm bắt đầu từ vị trí hiện tại và quấn quanh tệp thay vì chỉ sử dụng %? Tôi thấy không có cách sử dụng hợp lý cho dù bạn đang xem toàn bộ tệp.
tymik 22/02/18

@tymik Mục đích là bắt đầu tìm kiếm từ con trỏ và xem qua từng kết quả từng bước. Nhưng tôi đã không sử dụng Vim trong nhiều năm nay.
khốn nạn

Câu trả lời:


50

1.  Không khó để đạt được hành vi sử dụng thay thế hai bước:

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

Đầu tiên, lệnh thay thế được chạy cho mỗi dòng bắt đầu từ dòng hiện tại cho đến cuối tệp:

,$s/BEFORE/AFTER/gc

Sau đó, :substitutelệnh đó được lặp lại với cùng một mẫu tìm kiếm, chuỗi thay thế và cờ, sử dụng :& lệnh (xem :help :&):

1,''-&&

Tuy nhiên, sau đó, thực hiện thay thế trên phạm vi các dòng từ dòng đầu tiên của tệp đến dòng mà dấu ngữ cảnh trước đó đã được đặt, trừ đi một. Vì :substitutelệnh đầu tiên lưu trữ vị trí con trỏ trước khi bắt đầu thay thế thực tế, dòng được đánh địa chỉ bởi  ''là dòng hiện tại trước khi lệnh thay thế đó được chạy. ( '' Địa chỉ đề cập đến ' dấu hiệu giả; xem :help :range:help ''để biết chi tiết.)

Lưu ý rằng lệnh thứ hai (sau | dấu tách lệnh — xem :help :bar) không yêu cầu bất kỳ thay đổi nào khi mẫu hoặc cờ được thay đổi trong lệnh đầu tiên.

2.  Để lưu một số thao tác nhập, để hiển thị khung của lệnh thay thế ở trên trong dòng lệnh, người ta có thể xác định ánh xạ ở chế độ Bình thường, như sau:

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

Phần theo sau <c-b><right><right><right><right>là cần thiết để di chuyển con trỏ đến đầu dòng lệnh ( <c-b>) và sau đó bốn ký tự sang bên phải ( <right> × 4), do đó đặt nó giữa hai dấu gạch chéo đầu tiên, sẵn sàng cho người dùng bắt đầu nhập mẫu tìm kiếm . Khi mẫu mong muốn và mẫu thay thế đã sẵn sàng, lệnh kết quả có thể được chạy bằng cách nhấn Enter.

(Người ta có thể xem xét việc có //thay vì ///trong ánh xạ ở trên, nếu người ta thích nhập mẫu, thì hãy nhập dấu gạch chéo phân tách, theo sau là chuỗi thay thế, thay vì sử dụng mũi tên phải để di chuyển con trỏ qua dấu gạch chéo ngăn cách đã có sẵn bắt đầu phần thay thế.)


1
Vấn đề duy nhất với giải pháp này là các tùy chọn cuối cùng (l) và thoát (q) không ngăn lệnh thay thế thứ hai chạy
Steve Vermeulen

@eventualEntropy Xem giải pháp của tôi bên dưới về cách nhắc nhấn 'q' khác.
q335r49

2
Đừng quên (như tôi đã làm) để thoát khỏi đường ống nếu bạn đang đặt nó trong một ánh xạ khóa.
jazzabeanie

11
Ôi trời, tất cả những ký tự tùy tiện đó có ý nghĩa gì. Làm thế nào bạn học được điều đó?
rock raccoon

2
@Tropilio: Sau đó, bạn có thể thử một cái gì đó như thế này: :noremap <leader>R :,$s///gc\|1,''-&&<c-b><right><right><right><right>. Nó đưa :,$s///gc|1,''-&&vào dòng lệnh với con trỏ giữa hai dấu gạch chéo đầu tiên. Khi bạn nhập mẫu và thay thế mong muốn, bạn có thể chạy lệnh kết quả bằng cách nhấn Enter .
ib.

174

Bạn đang sử dụng một dải ô, %viết tắt của 1,$nghĩa là toàn bộ tệp. Để đi từ dòng hiện tại đến dòng cuối cùng mà bạn sử dụng .,$. Dấu chấm có nghĩa là dòng hiện tại và $có nghĩa là dòng cuối cùng. Vì vậy, lệnh sẽ là:

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

Nhưng dòng .hoặc dòng hiện tại có thể được giả định do đó có thể bị xóa:

:,$s/\vBEFORE/AFTER/gc

Để được trợ giúp thêm, hãy xem

:h range

Cảm ơn, tôi đã biết về điều đó. Tôi muốn tìm kiếm & thay thế kết thúc khi nó đi đến cuối tài liệu. Với:., $ S nó không làm được điều đó, nó chỉ thông báo "Không tìm thấy mẫu".
khốn nạn

7
Đối với "mười dòng tiếp theo":10:s/pattern/replace/gc
đây

10
mặc dù nó không phải là những gì OP đang tìm kiếm, điều này rất hữu ích đối với tôi, cảm ơn!
Tobias J

51

%là một phím tắt cho 1,$
(Vim help => :help :%bằng 1, $ (toàn bộ tệp).)

. là vị trí con trỏ để bạn có thể làm

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

Để thay thế từ đầu tài liệu đến con trỏ

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

Vân vân

Tôi thực sự khuyên bạn nên đọc hướng dẫn sử dụng về phạm vi :help rangevì hầu hết các lệnh đều hoạt động với phạm vi.


Cảm ơn, tôi đã biết về điều đó. Tôi muốn tìm kiếm & thay thế kết thúc khi nó đi đến cuối tài liệu. Với:., $ S nó không làm được điều đó, nó chỉ thông báo "Không tìm thấy mẫu".
khốn nạn

@basteln: có lỗi đánh máy trong bài // bạn đã copy và paste chưa?
sehe

Vì vậy, bạn muốn thay thế MỌI THỨ nhưng khi bạn muốn yêu cầu xác nhận, bạn muốn bắt đầu từ vị trí hiện tại. Chỉ cần làm điều đó hai lần sau đó.
mb14

bạn có thể sử dụng n và & thay thế.
mb14

hmm, tôi đoán n và & là giải pháp tốt nhất, mặc dù nó không chính xác như thế nào (với lời nhắc có / không trên mọi trận đấu). Nhưng chắc chắn đủ tốt, cảm ơn! :)
khốn nạn

4

CUỐI CÙNG, tôi đã đưa ra một giải pháp cho thực tế là việc thoát khỏi tìm kiếm sẽ kéo dài đến đầu tệp mà không cần viết một hàm lớn ...

Bạn sẽ không tin rằng tôi mất bao lâu để nghĩ ra điều này. Chỉ cần thêm lời nhắc có nên quấn hay không: nếu người dùng nhấn qlại, đừng quấn. Vì vậy, về cơ bản, hãy thoát khỏi tìm kiếm bằng cách chạm qqvào thay vì q! (Và nếu bạn muốn bọc, chỉ cần nhập y.)

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

Tôi thực sự đã ánh xạ điều này đến một phím nóng. Vì vậy, ví dụ: nếu bạn muốn tìm kiếm và thay thế mọi từ dưới con trỏ, bắt đầu từ vị trí hiện tại, bằng 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)

Theo ý kiến ​​của tôi, cách tiếp cận này — chèn thêm dấu nhắc vào giữa hai lệnh được đề xuất trong câu trả lời của tôi — sẽ làm tăng độ phức tạp không cần thiết mà không cải thiện khả năng sử dụng. Trong phiên bản gốc , nếu bạn thoát lệnh thay thế đầu tiên (với q, lhoặc Esc ) và không muốn tiếp tục từ trên cùng, bạn có thể nhấn qhoặc Esc một lần nữa để thoát lệnh thứ hai ngay lập tức. Nghĩa là, việc bổ sung lời nhắc được đề xuất dường như sao chép một cách hiệu quả chức năng đã có.
ib.

Anh bạn ... bạn đã THỬ cách tiếp cận của mình chưa? Giả sử tôi muốn thay thế 3 lần xuất hiện tiếp theo của "let" bằng "unlet", và sau đó - đây là chìa khóa - tiếp tục chỉnh sửa đoạn văn . Cách tiếp cận của bạn "Nhấn y, y, y, bây giờ nhấn q. Bây giờ bạn bắt đầu tìm kiếm ở dòng đầu tiên của đoạn văn. Nhấn lại q để thoát. Bây giờ quay lại vị trí trước đó, để tiếp tục chỉnh sửa. Ok, g ;. Vì vậy, về cơ bản: yyyqq ... trở nên bối rối ... g; (hoặc u ^ R) Phương pháp của tôi: yyyqq
q335r49

Tất nhiên yyyq, phương pháp lý tưởng là tiếp tục chỉnh sửa, không làm mất phương hướng. Nhưng yyyqqgần như là tốt, nếu bạn coi một lần nhấn đúp khá nhiều là một lần nhấn duy nhất về mức độ dễ sử dụng.
q335r49

Cả lệnh gốc và phiên bản của nó với lời nhắc đều không trả lại con trỏ về vị trí trước đó của nó trong trường hợp đó. Cái trước rời khỏi người dùng ở lần xuất hiện đầu tiên của mẫu từ trên xuống (vị trí ở thời điểm nhấn qlần thứ hai). Cái sau để lại con trỏ ở lần xuất hiện cuối cùng đã được thay thế (ngay trước khi lần đầu tiên qđược nhấn).
ib.

1
Trong phiên bản gốc , nếu nó là một lợi thế để tự động trả lại con trỏ đến vị trí trước đây của nó (không gõ sử dụng tổ hợp phím Ctrl + O ), người ta có thể chỉ cần nối thêm các norm!``lệnh: :,$s/BEFORE/AFTER/gc|1,''-&&|norm!``.
ib.

3

Đây là một cái gì đó rất khó giải quyết những lo ngại về việc kết hợp tìm kiếm với phương pháp tiếp cận hai bước ( :,$s/BEFORE/AFTER/gc|1,''-&&) hoặc với trung gian "Tiếp tục ở đầu tệp?" tiếp cận:

" 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

Hàm này sử dụng một vài thủ thuật để kiểm tra xem chúng ta có nên quấn quanh đầu hay không:

  • Không gói nếu người dùng nhấn "l", "q" hoặc "Esc", bất kỳ lệnh nào trong số đó cho thấy mong muốn hủy bỏ.
  • Phát hiện lần nhấn phím cuối cùng đó bằng cách ghi macro vào sổ đăng ký "s" và kiểm tra ký tự cuối cùng của nó.
  • Tránh ghi đè macro hiện có bằng cách lưu / khôi phục thanh ghi "s".
  • Nếu bạn đã ghi macro khi sử dụng lệnh, tất cả các cược sẽ bị tắt.
  • Cố gắng làm điều đúng khi bị gián đoạn.
  • Tránh cảnh báo "phạm vi ngược" với một biện pháp bảo vệ rõ ràng.

1
Tôi đã giải nén nó thành một plug-in nhỏ và đưa nó lên GitHub tại đây .
wincent

Chỉ cần cài đặt plugin của bạn, nó hoạt động như một sự quyến rũ !! Cảm ơn bạn rất nhiều vì đã làm điều này!
Tropilio

1

Tôi đến muộn bữa tiệc, nhưng tôi quá thường xuyên dựa vào các trình trả lời stackoverflow trễ như vậy nên không làm điều đó. Tôi đã thu thập các gợi ý về reddit và stackoverflow và tùy chọn tốt nhất là sử dụng \%>...cmẫu trong tìm kiếm, mẫu này chỉ khớp sau con trỏ của bạn.

Điều đó nói rằng, nó cũng làm rối mẫu cho bước thay thế tiếp theo và rất khó nhập. Để chống lại những tác động đó, một chức năng tùy chỉnh phải lọc mẫu tìm kiếm sau đó, do đó đặt lại nó. Xem bên dưới.

Tôi đã đấu tranh cho bản thân mình với một ánh xạ thay thế lần xuất hiện tiếp theo và chuyển đến lần sau, chứ không phải hơn (dù sao cũng là mục tiêu của tôi). Tôi chắc chắn rằng, dựa trên điều này, một sự thay thế toàn cầu có thể được thực hiện. Hãy lưu ý khi làm việc trên một giải pháp nhằm vào một cái gì đó tương tự như :%s/.../.../gvậy, mẫu bên dưới lọc ra các kết quả phù hợp ở tất cả các dòng còn lại vị trí con trỏ - nhưng sẽ bị xóa sau khi thay thế duy nhất hoàn thành, vì vậy nó sẽ mất tác dụng ngay sau đó, chuyển đến trận đấu tiếp theo và do đó có thể chạy qua tất cả các trận đấu một.

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


Cảm ơn, tôi đồng ý rằng một câu trả lời muộn thường hữu ích. Cá nhân tôi không sử dụng Vim nữa từ rất lâu rồi (vì những lý do như thế này), nhưng tôi chắc rằng rất nhiều người khác sẽ thấy điều này hữu ích.
khốn nạn
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.