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

发布于 2024-12-06 22:19:07 字数 298 浏览 0 评论 0原文

当我使用 / Normal-mode 命令进行搜索时:

/\vSEARCHTERM

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

:%s/\vBEFORE/AFTER/gc

Vim 相反从文件顶部开始。

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

When I search with the / Normal-mode command:

/\vSEARCHTERM

Vim starts the search from the cursor position and continues downwards, wrapping around to the top. However, when I search and replace using the :substitute command:

:%s/\vBEFORE/AFTER/gc

Vim starts at the top of the file, instead.

Is there a way to make Vim perform search and replace starting from the cursor position and wrapping around to the top once it reaches the end?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

煞人兵器 2024-12-13 22:19:07

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

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

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

:,$s/\vBEFORE/AFTER/gc

有关更多帮助,请参阅

:h range

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
小ぇ时光︴ 2024-12-13 22:19:07

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>

尾随 部分是必需的
将光标移动到命令行的开头 () 并
然后向右四个字符 ( × 4),
因此将其放在前两个斜线符号之间,为用户做好准备
开始输入搜索模式。一旦获得所需的图案和
替换已准备就绪,可以通过按运行生成的命令
输入

(人们可能会考虑在上面的映射中使用 // 而不是 ///
如果您喜欢先键入模式,然后键入分隔斜杠
oneself,后跟替换字符串,而不是使用
向右箭头将光标移动到已存在的分隔符上
替换部分开始的斜杠。)

1. It is not hard to achieve the behavior in question using
a two-step substitution:

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

First, the substitution command is run for each line starting from
the current one until the end of file:

,$s/BEFORE/AFTER/gc

Then, that :substitute command is repeated with the same search
pattern, replacement string, and flags, using the :& command
(see :help :&):

1,''-&&

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. (The '' address refers to the
' pseudo-mark; see :help :range and :help '' for details.)

Note that the second command (after the | command separator—see
:help :bar) does not require any change when the pattern or flags
are altered in the first one.

2. To save some typing, in order to bring up the skeleton of
the above substitution command in the command line, one can define
a Normal-mode mapping, like so:

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

The trailing <c-b><right><right><right><right> part is necessary
to move the cursor to the beginning of the command line (<c-b>) and
then four characters to the right (<right> × 4),
thus putting it between the first two slash signs, ready for the user
to start typing the search pattern. Once the desired pattern and the
replacement are ready, the resulting command can be run by pressing
Enter.

(One might consider having // instead of /// in the mapping above,
if one prefers to type the pattern and then type the separating slash
oneself, followed by the replacement string, instead of using the
right arrow to move the cursor over an already present separating
slash starting the replacement part.)

心在旅行 2024-12-13 22:19:07

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

. 是光标位置,所以你可以

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

从头开始替换文档直到光标

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

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

% 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.

清秋悲枫 2024-12-13 22:19:07

我终于想出了一个解决方案,解决了这样一个事实:退出搜索会回到文件的开头,而无需编写一个巨大的函数……

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

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)
无需解释 2024-12-13 22:19:07

这里有一些非常粗略的内容,解决了使用两步方法:,$ 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,则不会换行,其中任何一个都表示希望中止。
  • 通过将宏记录到寄存器并检查它的最后一个字符来检测最后一次按键。
  • 通过保存/恢复寄存器来避免覆盖现有宏。
  • 如果您在使用该命令时已经在录制宏,那么一切都将失败。
  • 尝试通过中断做正确的事情。
  • 通过明确的防护避免“向后范围”警告。

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?”-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

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.
夜吻♂芭芘 2024-12-13 22:19:07

我参加聚会迟到了,但我经常依赖迟到的 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

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文