Vim:从光标位置开始全局搜索和替换

问题描述 投票:112回答:6

当我搜索时

/\vSEARCHTERM

Vim从光标位置向下开始搜索,它将环绕到顶部。但是,当搜索和替换使用

:%s/\vBEFORE/AFTER/gc

Vim从文件的开头开始。

是否有一种方法可以使Vim从光标位置开始进行搜索和替换一旦到达末尾,它就会环绕到顶部?

search vim replace
6个回答
49
投票

使用两步替换实现行为并不难:

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

首先,从当前文件,直到文件末尾。然后,:substitute命令是重复相同的搜索模式,替换字符串和标志,使用:&命令(请参阅:help :&)。

但是,后者在行范围内执行替换从文件的第一行到上一个上下文所在的行标记已设置,减一。自第一个:substitute命令以来在开始实际替换之前存储光标位置,由''寻址的行是之前的当前行该替代命令已运行。 (''地址指的是'伪标记;有关详细信息,请参见:help :range:help ''。)

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

为了保存一些输入内容,以显示上面的框架在命令行中的替换命令,可以定义一个普通模式像这样映射:

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

尾随的<c-b><right><right><right><right>部分是必需的将光标放在前两个斜杠符号之间,为用户开始输入搜索模式。一旦所需的模式并准备好替换,可以通过以下命令运行结果命令按Enter

((在上面的映射中,您可能考虑使用//而不是///,如果喜欢键入图案,则键入分隔斜杠自己,然后是替换字符串,而不是使用向右箭头将光标移到已经存在的分隔符上斜线开始更换零件。)


168
投票

您已经在使用范围%,这是1,$的缩写,表示整个文件。要从当前行到末尾,请使用.,$。句点表示当前行,$表示最后一行。因此命令将是:

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

但是可以假定.或当前行,因此可以将其删除:

:,$s/\vBEFORE/AFTER/gc

有关更多帮助,请参阅

:h range

49
投票

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

.是光标所在的位置,因此可以执行

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

从文档的开头到光标的替换

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

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


4
投票

我终于想出了一个解决方案,即退出搜索会绕到文件的开头而无需编写巨大的功能...

您不会相信我花了多长时间才想到这个。只需添加一个是否要换行的提示即可:如果用户再次按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)

3
投票

[这很粗糙,解决了使用the two-step approach:,$s/BEFORE/AFTER/gc|1,''-&&)或中间"Continue at beginning of file?"方法包装搜索的问题:

" 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

[此函数使用了一些技巧来检查是否应该绕到顶部:

  • 如果用户按下“ l”,“ q”或“ Esc”,则表示不希望中止。
  • 通过将宏记录到“ s”寄存器中并检查其最后一个字符来检测最后的按键按下。
  • 通过保存/恢复“ s”寄存器避免覆盖现有的宏。
  • 如果使用该命令时已经在录制宏,则所有选择均关闭。
  • 尝试使用中断做正确的事情。
  • [在有明确防护的情况下避免“向后范围”警告。

我参加聚会的时间很晚,但是我经常依赖于如此晚的stackoverflow回答者而不去做。我收集了有关reddit和stackoverflow的提示,最好的选择是在搜索中使用\%>...c模式,该模式仅在光标之后才匹配。

就是说,它也弄乱了下一个替换步骤的模式,很难键入。为了应对这些影响,自定义函数必须随后过滤搜索模式,然后将其重置。见下文。

我已经用一个映射来竞争自己,该映射替换了下一个事件并在之后跳到下一个事件,并且不超过(无论如何,这是我的目标)。我相信,在此基础上,可以制定出全球替代方案。在针对:%s/.../.../g之类的解决方案进行工作时,请牢记以下模式会过滤掉光标位置左侧all

行中的匹配项-但在单次替换完成后会被清除,因此会丢失之后立即生效,跳到下一场比赛,因此可以一个接一个地进行所有比赛。

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


1
投票

我参加聚会的时间很晚,但是我经常依赖于如此晚的stackoverflow回答者而不去做。我收集了有关reddit和stackoverflow的提示,最好的选择是在搜索中使用\%>...c模式,该模式仅在光标之后才匹配。

© www.soinside.com 2019 - 2024. All rights reserved.