在Vim中全局搜索和替换,从光标位置开始并环绕


112

当我使用/ 普通模式命令搜索时:

/\vSEARCHTERM

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

:%s/\vBEFORE/AFTER/gc

Vim从文件开头开始。

有没有办法使Vim从光标位置开始执行搜索和替换,在到达末尾时环绕到顶部?


2
\vpattern-“非常魔术”模式:非字母数字字符被解释为特殊的正则表达式符号(无需转义)
Carlos Araya

从当前位置开始并环绕文件而不是使用just的意义是%什么?我认为没有合理的用处,因为您无论如何都要遍历整个文件。
tymik '18

@tymik的目的是从光标开始搜索,并逐步浏览每个结果。但是我已经好多年没有使用Vim了。
basteln

Answers:


50

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

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

首先,从当前行开始到文件末尾的每一行都执行替换命令:

,$s/BEFORE/AFTER/gc

然后,使用以下:substitute命令以相同的搜索模式,替换字符串和标志重复该:& 命令:help :&

1,''-&&

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

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

2.  为了节省一些键入内容,为了在命令行中显示上述替换命令的框架,可以定义一个普通模式映射,如下所示:

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

尾部<c-b><right><right><right><right>必须将光标移动到命令行的开头(<c-b>),然后再将其向右移动四个字符(<right> ×4),从而将其置于前两个斜杠符号之间,以便用户开始键入搜索模式。一旦所需的样式和替换准备就绪,可以按来运行结果命令 Enter

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


1
该解决方案的唯一问题是,选项last(l)和quit(q)不会阻止第二个替代命令的运行
Steve Vermeulen 2013年

@eventualEntropy有关提示再次按“ q”的信息,请参见下面的解决方案。
q335r49 2014年

2
如果您将其放入键映射中,请不要忘记(就像我一样)将管道转义。
jazzabeanie '16

11
噢,天哪,所有这些任意字符甚至意味着什么。你是怎么学到的?
岩石浣熊

2
@Tropilio:然后您可以尝试如下操作::noremap <leader>R :,$s///gc\|1,''-&&<c-b><right><right><right><right>。它将:,$s///gc|1,''-&&光标置于命令行的前两个斜杠符号之间。键入所需的模式并替换后,可以按Enter键运行结果命令。
ib。

174

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

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

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

:,$s/\vBEFORE/AFTER/gc

有关更多帮助,请参见

:h range

谢谢,我已经知道了。我希望搜索和替换在到达文档末尾时自动换行。使用:。,$ s不会这样做,它只会显示“找不到模式”。
basteln 2011年

7
对于“接下来的十行”:10:s/pattern/replace/gc
此处

10
即使这不是OP想要的,这对我也很有帮助,谢谢!
Tobias J

51

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

. 是光标位置,因此您可以

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

从文档开头到光标的替换

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

等等

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


谢谢,我已经知道了。我希望搜索和替换在到达文档末尾时自动换行。使用:。,$ s不会这样做,它只会显示“找不到模式”。
basteln 2011年

@basteln:帖子中有错字// //您复制并粘贴了吗?
sehe 2011年

因此,您想替换所有内容,但要询问确认时,要从当前位置开始。然后再做两次。
mb14 2011年

您可以使用n和&代替。
mb14 2011年

嗯,我猜n和&是最好的解决方案,尽管它并不完全是应该的(每次比赛都有是/否提示)。但是绝对足够好,谢谢!:)
basteln 2011年

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)

我认为,这种方法(在我的答案中建议的两个命令之间插入额外的提示)会增加不必要的复杂性,而不会提高可用性。在原始版本中,如果您退出第一个替换命令(使用q,,lEsc)并且不想从顶部继续,则已经可以再次按qEsc来立即退出第二个命令。也就是说,建议的提示添加似乎有效地复制了已经存在的功能。
ib。

杜德...你尝试过你的方法吗?假设我想用“ unlet”替换下3个出现的“ let”,然后-这是关键- 继续编辑该段落。您的方法“按y,y,y,现在按q。现在您开始搜索该段落的第一行。再次按q退出。现在返回到之前的位置,继续编辑。确定,g;。因此,基本上:yyyqq ...感到困惑... g;(或u ^ R)我的方法:yyyqq
q335r49 2014年

当然yyyq,理想的方法是继续编辑,而不会使方向混乱。但是yyyqq,如果您考虑使用双击,则几乎可以说是一次点击,效果几乎一样好。
q335r49 2014年

原始命令或其带有提示的版本都不会将光标返回到该场景中的先前位置。前者在第一次出现图案时从顶部离开用户(在q第二次按下时处于该位置)。后者将光标留在被替换的最后一个出现的位置(恰好在第一次q按下之前)。
ib。

1
原始版本中,如果希望自动将光标返回到其先前位置(无需用户输入Ctrl + O),则只需追加以下norm!``命令即可::,$s/BEFORE/AFTER/gc|1,''-&&|norm!``
ib。

3

这是一个非常粗糙的内容,它解决了使用两步方法:,$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

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

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

1
我将其提取到一个小插件中,并将其放在GitHub上
Wincent '16

刚刚安装了插件,它就像魅力一样!!非常感谢您这样做!
Tropilio

1

我参加晚会很晚,但是我经常依赖于如此晚的stackoverflow应答程序而不这样做。我收集了有关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


谢谢,我同意延迟回答通常会有所帮助。我个人已经很长时间不使用Vim了(由于这样的原因),但是我敢肯定,很多其他人会发现这很有用。
basteln
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.