可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
When I search with
/\vSEARCHTERM
Vim starts the search from the cursor position downwards, and it will wrap around to the top. However when search & replace using
:%s/\vBEFORE/AFTER/gc
Vim starts at the top of the file.
Is there a way to make Vim search & replace starting from the cursor position AND wrapping around to the top once it reaches the end?
回答1:
It is not hard to achieve the behavior using a two step substitution:
:,$s/BEFORE/AFTER/gc|1,''-&&
First, a substitution command is run for each line starting from the
current one until the end of file. Then, that :substitute
command is
repeated with the same search pattern, replacement string and flags,
using the :&
command. The latter, however, performs the substitution
on the range of lines from the first line of the file to the line where
the previous context mark has been set, minus one. Since the first
:substitute
command stores the cursor position before starting actual
replacements, the line addressed by ''
is the line that was the
current one before that substitution command was run.
Note that the second command (after the |
symbol) does not require any
change when pattern or flags of the first one are altered.
回答2:
You are already using a range, %
, which is short hand for 1,$
meaning the entire file. To go from the current line to the end you use .,$
. The period means current line and $
means the last line. So the command would be:
:.,$s/\vBEFORE/AFTER/gc
But the .
or current line can be assumed therefore can be removed:
:,$s/\vBEFORE/AFTER/gc
For more help see
:h range
回答3:
%
is a shortcut for 1,$
( Vim help => :help :%
equal to 1,$ (the entire file).)
.
is the cursor postion so you can do
:.,$s/\vBEFORE/AFTER/gc
To replace from the beginning of the document till the cursor
:1,.s/\vBEFORE/AFTER/gc
etc
I strongly suggest you read the manual about range :help range
as pretty much all commands work with a range.
回答4:
I've FINALLY come up with a solution to the fact that quitting the search wraps around to the beginning of the file without writing an enormous function...
You wouldn't believe how long it took me to come up with this. Simply add a prompt whether to wrap: if the user presses q
again, don't wrap. So basically, quit search by tapping qq
instead of q
! (And if you do want to wrap, just type y
.)
:,$s/BEFORE/AFTER/gce|echo 'Continue at beginning of file? (y/q)'|if getchar()!=113|1,''-&&|en
I actually have this mapped to a hotkey. So, for example, if you want to search and replace every word under the cursor, starting from the current position, with 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)
回答5:
Here's something very rough that addresses the concerns about wrapping the search around with the two-step approach (:,$s/BEFORE/AFTER/gc|1,''-&&
) or with an intermediate "Continue at beginning of file?" approach:
" 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
This function uses a couple of hacks to check whether we should wrap around to the top:
- No wrapping if user pressed "l", "q" or "Esc", any of which indicate a desire to abort.
- Detect that final key press by recording a macro into the "s" register and inspecting last character of it.
- Avoid overwriting an existing macro by saving/restoring the "s" register.
- If you are already recording a macro when using the command, all bets are off.
- Tries to do the right thing with interrupts.
- Avoids "backwards range" warnings with an explicit guard.
回答6:
I am late to the party, but I relied too often on such late stackoverflow answerrs to not do it. I gathered hints on reddit and stackoverflow, and the best option is to use the \%>...c
pattern in the search, which matches only after your cursor.
That said, it also messes up the pattern for the next replacement step, and is hard to type. To counter those effects, a custom function must filter the search pattern afterwards, thus resetting it. See below.
I have contended myself with a mapping that replaces the next occurence and jumps to the following after, and not more (was my goal anyway). I am sure, building on this, a global substitution can be worked out. Keep in mind when working on a solution aiming at something like :%s/.../.../g
that the pattern below filters out matches in all lines left to the cursor position -- but is cleaned up after the single substitution completes, so it loses that effect directly after, jumps to the next match and thus is able to run through all matches one by one.
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