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

Posted

技术标签:

【中文标题】如何在 Vim 中的单个命令调用中从光标位置开始并环绕文件末尾进行全局搜索和替换?【英文标题】:How to search and replace globally, starting from the cursor position and wrapping around the end of file, in a single command invocation in Vim? 【发布时间】:2011-11-27 17:28:06 【问题描述】:

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

/\vSEARCHTERM

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

:%s/\vBEFORE/AFTER/gc

Vim 从文件顶部开始。

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

【问题讨论】:

\vpattern - '非常神奇' 模式:非字母数字字符被解释为特殊的正则表达式符号(无需转义) 从当前位置开始并环绕文件而不是仅使用 % 有什么意义?无论如何,我认为您正在浏览整个文件没有任何合理的用途。 @tymik 目的是从光标开始搜索,并逐步浏览每个结果。但我已经好几年没用过 Vim了。 【参考方案1】:

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

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

. 或当前行可以被假定,因此可以删除:

:,$s/\vBEFORE/AFTER/gc

有关更多帮助,请参阅

:h range

【讨论】:

谢谢,我已经知道了。我希望搜索和替换在到达文档末尾时环绕。使用 :.,$s 它不会那样做,它只是说“找不到模式”。 对于“接下来的十行”:10:s/pattern/replace/gc 尽管这不是 OP 想要的,但这对我很有帮助,谢谢!【参考方案2】:

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>

结尾的&lt;c-b&gt;&lt;right&gt;&lt;right&gt;&lt;right&gt;&lt;right&gt; 部分是必需的 将光标移动到命令行的开头 (&lt;c-b&gt;) 和 然后向右四个字符(&lt;right&gt; × 4), 因此将其放在前两个斜线符号之间,为用户准备好 开始输入搜索模式。一旦所需的模式和 替换已准备好,可以通过按来运行生成的命令 输入

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

【讨论】:

此解决方案的唯一问题是选项 last (l) 和 quit (q) 不会阻止第二个替换命令运行 @eventualEntropy 请参阅下面关于提示再按一次“q”的解决方案。 如果您将其放入键映射中,请不要忘记(像我一样)转义管道。 天啊,这些随意的字符到底是什么意思。你是怎么知道的? @Tropilio:然后你可以尝试这样的事情::noremap &lt;leader&gt;R :,$s///gc\|1,''-&amp;&amp;&lt;c-b&gt;&lt;right&gt;&lt;right&gt;&lt;right&gt;&lt;right&gt;。它将:,$s///gc|1,''-&amp;&amp; 放入命令行,光标位于前两个斜杠符号之间。键入所需的模式和替换后,您可以按 Enter 运行生成的命令。【参考方案3】:

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

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

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

从文档开头到光标处替换

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

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

【讨论】:

谢谢,我已经知道了。我希望搜索和替换在到达文档末尾时环绕。使用 :.,$s 它不会那样做,它只是说“找不到模式”。 @basteln:帖子中有错字 // 你复制粘贴了吗? 所以你想替换所有东西,但是你想确认你想从当前位置开始。那就做两次吧。 你可以使用 n 和 & 来代替。 嗯,我猜 n 和 & 是最好的解决方案,尽管它不完全应该是这样(每场比赛都有是/否提示)。但绝对足够好,谢谢! :)【参考方案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)

【讨论】:

在我看来,这种方法——在my answer 中提出的两个命令之间插入额外的提示——增加了不必要的复杂性,却没有提高可用性。在original version 中,如果您退出第一个替换命令(使用qlEsc)并且不想从顶部继续,您已经可以按qEsc 再次立即退出第二个命令。也就是说,建议添加的提示似乎有效地复制了已经存在的功能。 老兄...你试过你的方法了吗?假设我想用“unlet”替换接下来出现的 3 个“let”,然后 -- 这是关键 -- 继续编辑段落。您的方法“按 y,y,y,现在按 q。现在您从段落的第一行开始搜索。再次按 q 退出。现在回到之前的位置,继续编辑。好的,g;。所以,基本上:yyyqq...变得困惑... g;(或u^R)我的方法:yyyqq 理想的方法当然是yyyq,继续编辑,没有迷失方向的跳转。但是yyyqq 几乎一样好,如果您认为双击就易用性而言几乎是单击。 原始命令及其带有提示的版本都不会将光标返回到该场景中的先前位置。前者在第一次出现模式时从顶部离开用户(第二次按下q 时的位置)。后者将光标留在最后一次被替换的位置(就在第一个 q 被按下之前)。 在the original version中,如果希望自动将光标返回到其先前位置(无需用户键入Ctrl + O),只需附加norm!`` 命令::,$s/BEFORE/AFTER/gc|1,''-&amp;&amp;|norm!``【参考方案5】:

这里有一些非常粗略的内容,解决了使用the two-step approach (:,$s/BEFORE/AFTER/gc|1,''-&amp;&amp;) 或中间“Continue at beginning of file?”-prompt 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

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

如果用户按下 LQEsc(其中任何一个都表示希望中止),则不会换行。 通过将宏记录到 s 寄存器并检查其最后一个字符来检测最后一次按键。 通过保存/恢复s 寄存器来避免覆盖现有宏。 如果您在使用命令时已经在录制宏,则所有赌注都将关闭。 尝试用中断做正确的事。 通过明确的保护避免“向后范围”警告。

【讨论】:

我把它解压成一个小插件和put it on GitHub here。 刚刚安装了你的插件,它就像魅力一样!非常感谢你制作这个!【参考方案6】:

我参加聚会迟到了,但我经常依赖这些迟到的 *** 回答者来不这样做。我收集了关于 reddit 和 *** 的提示,最好的选择是在搜索中使用 \%&gt;...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

【讨论】:

谢谢,我同意迟到的答案通常会有所帮助。我个人已经很长时间不再使用 Vim(出于诸如此类的原因),但我相信很多其他人会发现这很有帮助。

以上是关于如何在 Vim 中的单个命令调用中从光标位置开始并环绕文件末尾进行全局搜索和替换?的主要内容,如果未能解决你的问题,请参考以下文章

vim编辑器初级

Linux就该这么学--命令集合10(vim编辑器)

vim命令大全

如何使用linux系统vim中的复制,粘贴和删除

vim操作

vim里一个函数很长(几百行),光标在函数中间的某个位置,怎么快速跳到函数头?