(Neo)vim 替换块/正规可视范围,而不是行可视范围

huangapple go评论46阅读模式
英文:

(Neo)vim substitute over block/regular visual range, and not line visual range

问题

如果我在Vim的可视模式下突出显示一块文本,然后按下 : 键,我可以在它上面运行一个命令。例如:

  • :'<,'>s/foo/bar/g 用于替换,
  • :'<,'> !bash 将这些行发送到 bash shell 并获取输出,
  • :'<,'>sort 对这些行进行排序,
  • :'<,'> !some_cli_util 将这些行发送到 some_cli_util

如果我要执行的文本恰好位于单独的一行上,这是可以的,但通常情况下并非如此。例如,如果我正在编写文档并想要示例CLI命令及其输出,那么只需编写:

$ head requirements.txt

然后选择文本 head requirements.txt,输入 : !sh<CR>,就可以得到实际命令的输出。但实际上,当我选择 head requirements.txt 并按下 : 键时,我的选择会扩展为完整行选择,这会导致前导的 $ 符号与实际命令一起被覆盖,因此由于前导的 $ 符号,命令会失败。

一个不太琐碎的例子是,如果我有一个LaTeX表格,如下所示:

\begin{table}
    \begin{tabular}{lllll}
        col1   &amp; col2   &amp; col3   &amp; col4   &amp; col5     \\

        foo19s &amp; fook2e &amp; foojd9 &amp; alfooc &amp; 9dfooj   \\ 
        9foo1s &amp; ekfoo2 &amp; lqfoo  &amp; alfoo  &amp; 9efoo9s  \\ 
        1sfoo9 &amp; d9fooe &amp; fooj9d &amp; alsdfo &amp; 9hfooje  \\ 
        19sfoo &amp; 9dfooe &amp; lqfoo  &amp; dfoo9e &amp; ajfoo   
    \end{tabular}
\end{table}

我想将所有 foo 替换为 bar,但只在标题为 col2 的列中。如果我只能块选择 col2,然后执行替代命令 :<,&gt;s/foo/bar/g 就可以了。但当前,如果我块选择了 col2,然后按 : 键,Vim会自动选择我的块选择范围内的所有行。

理想情况下,我希望Vim足够智能,如果我在块可视模式下按下 : 键,它将使用 :<,&gt; 范围选择器,但如果我在行可视模式下按下 : 键,则它将使用 :&#39;&lt;,&#39;&gt; 范围选择器。

:h <告诉我 &lt; >` 标记确实存在,但我无法看到如何将它们应用于命令。

我可以做什么来启用Vim(或Neovim)以在块选择上执行命令?或者是否有关于这个问题的开放问题?

英文:

If I highlight a block of text in visual mode in Vim and then press :, I can run a command on it. For example

  • :&#39;&lt;,&#39;&gt;s/foo/bar/g to substitute,
  • or :&#39;&lt;,&#39;&gt; !bash to send the lines to the bash shell and get the output,
  • or :&#39;&lt;,&#39;&gt;sort to sort those lines,
  • or :&#39;&lt;,&#39;&gt; !some_cli_util to send those lines to some_cli_util

This is fine if the text I want to execute is conveniently on it's own line, but often that isn't the case. For example, if I'm writing docs and want to give and example of a cli command and it's output, then it would be nice to just write:

$ head requirements.txt

and then to select the text head requirements.txt, type : !sh&lt;CR&gt;, and be given the actual output of the command. But in reality, when I select head requirements.txt and type :, then my selection gets expanded to a full-line selection, which causes the leading $ to be clobbered in with the actual command, and so because of the leading $, the command fails.

A less trivial example of where this would be useful, is if I had a LaTeX table like:

\begin{table}
    \begin{tabular}{lllll}
        col1   &amp; col2   &amp; col3   &amp; col4   &amp; col5     \\

        foo19s &amp; fook2e &amp; foojd9 &amp; alfooc &amp; 9dfooj   \\
        9foo1s &amp; ekfoo2 &amp; lqfoo  &amp; alfoo  &amp; 9efoo9s  \\
        1sfoo9 &amp; d9fooe &amp; fooj9d &amp; alsdfo &amp; 9hfooje  \\
        19sfoo &amp; 9dfooe &amp; lqfoo  &amp; dfoo9e &amp; ajfoo   
    \end{tabular}
\end{table}

and I wanted to replace all the instances of foo with bar, but only in the columns titled col2. If I could block-select only col2, and then execute a substitute command like :`&lt;,`&gt;s/foo/bar/g then this would work. But currently, if I block select only col2 and then press :, Vim will auto-select the entire range of lines that were in my block selection.

Ideally, I'd like vim to be smart enough such that if am in block-visual mode and press :, then it'll use the :`&lt;,`&gt; range selector, but if I'm in line-visual mode and press : then it'll use the :&#39;&lt;,&#39;&gt; range selector.

:h `&lt; tells me that the `&lt; and `&gt; marks do exist, but I can't see how to apply them to commands.

What can I do to enable vim (or neovim) to execute commands on block-selections? or is there an issue open for this problem?

答案1

得分: 2

> 我不能只使用常规的可视行选择器 :&#39;&lt;,&#39;&gt;s/ba/__/g,因为那会不正确地将所有的 baz 替换为 __z

是的,你绝对可以,但要使用更精细的搜索模式:

:[range]s/ba\zer/__/g

请参考 :help \ze

--- 编辑 ---

正如另一个答案中所解释的,Ex 命令只适用于,范围是的范围。你无法将除了一行或多行之外的其他内容传递给 :help :s:help :range!:help :sort

没有绕过这一点的方法

虽然Ex 命令的范围始终是逐行的,但其中一些命令可能以某种方式遵守可视选择。

:help :s的情况下,你可以再次使用更精细的搜索模式:

&lt;C-v&gt;&lt;motions&gt;
:[range]s/\%V.*\zsfoo\ze.*%V/bar

请参考 :help \%V

英文:

> I can't just use the regular visual-line selector :&#39;&lt;,&#39;&gt;s/ba/__/g because that will incorrectly replace all the bazs with __zs.

Yes, you definitely can, but with a more refined search pattern:

:[range]s/ba\zer/__/g

See :help \ze.

--- EDIT ---

As explained in the other answer, Ex commands only work on lines and ranges are ranges of lines. You wont be able to pass something else than one or several lines to :help :s, :help :range! or :help :sort.

There is no way around that.

While the range of Ex commands is always linewise, some of them may honor the visual selection in some way.

In the case of :help :s, what you can do is, again, use a more refined search pattern:

&lt;C-v&gt;&lt;motions&gt;
:[range]s/\%V.*\zsfoo\ze.*%V/bar

See :help \%V.

答案2

得分: 1

如何完成这个任务在romainl的回答中已经完美描述了。然而,我想回答一下“为什么要这样做?”。

你写道:
> 理想情况下,我希望Vim足够智能,以至于如果我在块可视模式下按:,那么它将使用:<,>范围选择器,但如果我在行可视模式下按:,它将使用:'<,>'范围选择器。

这种行为目前是不可能的,至少在现在(Vim 9.0)是不可能的。引用自:h :visual_example
> 目前,“:”命令只对整行起作用。当你选择一行的一部分,执行像“:!date”这样的操作将替换整行。如果你只想替换一行的一部分,你将不得不为它创建一个映射。在未来的版本中,“:”可能会在部分行上起作用。

同样,:h :s中也有(重点是):
> 对于[range]中的每一,替换{pattern}的匹配项

人们普遍误解Vim只操作(块)可视选择。并不是所有命令都是如此。相反,它们操作被选中的

所以,你将不得不使用类似于(正如romainl所指出的,为了完整起见):s/ba\zer/__/g这样的行替换。

英文:

How to get it done is described perfectly in romainl's answer. I'd like to answer the "why?", though.

You wrote:
> Ideally, I'd like vim to be smart enough such that if am in block-visual mode and press :, then it'll use the :`<,`> range selector, but if I'm in line-visual mode and press : then it'll use the :'<,'> range selector.

This behavior is explicitly impossible, at least for now (Vim 9.0). Quoting from :h :visual_example:
> Currently the ":" command works on whole lines only. When you select part of
> a line, doing something like ":!date" will replace the whole line. If you
> want only part of the line to be replaced you will have to make a mapping for
> it. In a future release ":" may work on partial lines.

Likewise, :h :s says (emphasis mine):
> For each line in [range] replace a match of {pattern}

It is a common misconception that Vim would operate on the (block) visual selection only. Not all commands do. Instead, they operate on lines that are selected.

So you'll have to use a line-wise substitution like (as pointed out by romainl and included for the sake of completeness) :s/ba\zer/__/g.

答案3

得分: 1

这是一个合理的工具查找,但正如其他人指出的,范围仅适用于逐行。对于那些希望将选择内容用作外部程序参数的情况,我建议将其复制并粘贴到命令行中。

一个可能为您提供通用解决方案的插件是 vis:https://github.com/vim-scripts/vis。而不是:&#39;&lt;,&#39;&gt;s/foo/bar/g,您可以执行:&#39;&lt;,&#39;&gt;B s/foo/bar/g,这不太方便,但可以完成任务。

英文:

It's a reasonable tool to look for, but as others have pointed out, ranges are only line-wise. For the cases where you'd like to use a selection as an argument to an external program, I'd recommend yanking and pasting in the command-line.

A plugin that might give you a general-purpose solution is vis: https://github.com/vim-scripts/vis. Instead of :&#39;&lt;,&#39;&gt;s/foo/bar/g, you'd execute :&#39;&lt;,&#39;&gt;B s/foo/bar/g, which is less convenient, but will do the trick.

huangapple
  • 本文由 发表于 2023年2月16日 18:15:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/75470770.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定