如何在 Vim 中的单个命令调用中从光标位置开始并包装文件末尾进行全局搜索和替换?

当我使用 /正常模式命令进行搜索时:

/\vSEARCHTERM

Vim 从光标位置开始搜索,然后继续向下,绕到顶部。但是,当我使用 :substitute命令进行搜索和替换时:

:%s/\vBEFORE/AFTER/gc

相反,Vim 从文件的顶部开始。

有没有一种方法可以让 Vim 执行搜索和替换,从光标位置 还有到达顶部开始?

72367 次浏览

%1,$的快捷方式
(Vim help = > :help :%等于1,$(整个文件))

.是光标的位置,所以你可以这样做

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

从文档开始到光标之间进行替换

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

等等

我强烈建议您阅读有关范围 :help range的手册,因为几乎所有的命令都与范围有关。

您已经在使用范围 %,它是 1,$的简写,意思是整个文件。从当前行到结束使用 .,$。句点表示当前行,$表示最后一行。所以命令应该是:

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

但是,可以假定 .或当前线路因此可以删除:

:,$s/\vBEFORE/AFTER/gc

更多帮助请看

:h range

1. 这并不难实现有问题的行为 两步替代:

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

首先,对于从 直至文件结束为止的当前文件:

,$s/BEFORE/AFTER/gc

然后,在相同的搜索中重复该 :substitute命令 模式、替换字符串和标志,使用 :&命令 (见 :help :&) :

1,''-&&

然而,后者在行的范围上执行替换 从文件的第一行到上一个上下文所在的行 标记已设置,负一。自从第一个 :substitute命令 在开始实际替换之前存储光标位置,则 由 ''寻址的线是之前的线 运行了替换命令。(''地址引用 '伪标记; 详见 :help :range:help ''。)

请注意,第二个命令(在 |命令分隔符之后)请参见 :help :bar)不需要任何改变时的模式或标志 在第一个中被改变了。

为了节省一些输入,为了打开骨架 如果在命令行中使用上述替换命令,则可以定义 一个标准模式映射,如下所示:

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

后面的 <c-b><right><right><right><right>部分是必要的 将光标移动到命令行的开头(<c-b>) ,然后 然后右边四个字(<right> × 4) , 因此将它放在前两个斜杠符号之间,为用户准备好 开始键入搜索模式 替换已准备好,可以通过按下 Enter.

(可以考虑在上面的映射中使用 //而不是 ///, 如果喜欢输入模式,那么输入分隔斜杠 自身,后跟替换字符串,而不是使用 向右箭头将光标移动到已经存在的分隔符上 斜线开始替换部分。)

我终于想出了一个解决方案,这个方案可以在不编写大量函数的情况下,从搜索结束一直到文件的开头。

你不会相信我花了多久才想出这个。只需添加一个是否换行的提示: 如果用户再次按 q,则不要换行。因此,基本上,退出搜索点击 qq而不是 q!(如果你想换行,只要输入 y就可以了。)

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

我把这个映射到了一个热键上。例如,如果您想搜索并替换光标下的每个单词,从当前位置开始,使用 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)

这里有一些非常粗略的内容,可以解决使用 两步法(:,$s/BEFORE/AFTER/gc|1,''-&&)或中间 “在文件开始处继续?”-提示方法包装搜索的问题:

" 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

这个函数使用了一些技巧来检查我们是否应该绕到顶部:

  • 如果用户按下 LQEsc,则不包装,其中任何一个都表示希望中止。
  • 通过将一个宏记录到 s寄存器并检查其最后一个字符来检测最后一个按键。
  • 通过保存/恢复 s寄存器避免覆盖现有的宏。
  • 如果您在使用该命令时已经在录制宏,则所有赌注都将取消。
  • 试图用中断做正确的事情。
  • 使用显式警卫避免“向后范围”警告。

我参加聚会已经迟到了,但是我经常依赖这种迟到的堆栈溢出答案来避免这种情况发生。我收集了关于 reddit 和 stackoverflow 的提示,最好的选择是在搜索中使用 \%>...c模式,它只在您的光标之后匹配。

也就是说,它也搞乱了下一个替换步骤的模式,并且很难输入。为了抵消这些效果,自定义函数必须事后过滤搜索模式,从而重置它。请看下面。

我已经为自己争取了一个映射,它取代了下一个事件,并跳转到下一个事件,而不是更多(无论如何,这是我的目标)。我相信,在此基础上,我们可以找到一种全球替代方案。在解决像 :%s/.../.../g这样的问题时,请记住,下面的模式会过滤掉光标位置留下的 所有行中的匹配项,但是在单次替换完成后会被清除,所以它会在下一次匹配之后直接失去这种效果,跳转到下一次匹配,因此能够一个一个地运行所有匹配项。

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