如何创建递归宏,使其仅在行尾运行?

或如何运行递归宏,直到行尾?

#1 楼

也许有一个更简单的方法,但是也许您可以尝试以下方法。

假设您将使用寄存器q来记录您的递归宏。

在记录的开始,键入:

:let a = line('.')


然后,在记录的最后,而不是敲击@q来使宏递归,请键入以下命令:

:if line('.') == a | exe 'norm @q' | endif


最后用q结束宏的记录。

您键入的最后一条命令将重播宏qexe 'norm @q'),但前提是当前行号(line('.'))与最初存储在变量a中的相同。

:normal命令允许您从Ex模式键入普通命令(如@q)。
以及该命令的原因被包装为字符串并由命令:execute执行,以防止:normal消耗(键入)该命令的其余部分(|endif)。


用法示例。

假设您具有以下缓冲区:

1 2 3 4
1 2 3 4
1 2 3 4
1 2 3 4


您想使用递归宏从任意行中递增所有数字。

您可以键入0将光标移至行首,然后开始录制宏:

qqq
qq
:let a=line('.')
<C-a>
w
:if line('.')==a|exe 'norm @q'|endif
q




qqq清除寄存器q的内容,以便当您在宏定义期间首次调用它时,它不会干扰

qq开始记录

:let a=line('.')存储变量a中的当前行号


Ctrl + a增大光标下方的数字

w将光标移至下一个数字

:if line('.')==a|exe 'norm @q'|endif调用宏,但前提是
行号未更改

q停止记录

定义宏后,如果将光标放在第三行,请按0将其移至行的开头,然后按@q以重播宏q,它只会影响当前行,而不会影响其他行:

1 2 3 4
1 2 3 4
2 3 4 5
1 2 3 4




记录后进行宏递归

如果需要,可以在使用宏进行记录后递归它存储在寄存器中的字符串中,并且您可以用点.运算符连接两个字符串。

这将为您带来一些好处:


无需在录制之前清除寄存器,因为在定义宏之后将在宏中添加字符@q,并且在覆盖了所有旧内容之后
在录制期间无需键入任何异常,您可以集中精力制作一个简单的可运行的宏
,在递归查看之前对其进行测试它的行为方式

如果像往常一样(非递归地)记录宏,则可以使用以下命令使其递归:

let @q = @q . "@q"


甚至更短:let @q .= "@q" .=是允许将字符串追加到另一个字符串的运算符。

这应该在存储在寄存器@q内的击键序列的最后添加2个字符q
您还可以定义一个自定义命令:

command! -register RecursiveMacro let @<reg> .= "@<reg>"


它定义命令:RecursiveMacro,该命令等待寄存器名称作为参数(因为-register属性传递给:command)。
与以前的命令相同,唯一的区别是您将每次出现的q替换为<reg>。当执行该命令时,Vim会使用您提供的寄存器名称自动扩展<reg>的每次出现。

现在,您所需要做的就是照常记录您的宏(非递归),然后键入:RecursiveMacro q以使该宏在递归寄存器q中的存储。


您可以执行相同的操作使其在当前行上保留的条件下进行宏递归:录制之后。您只需串联两个字符串,然后按一下q寄存器当前包含的任何按键,就可以在字符串之前和之后进行连接:

let @q =重新定义了寄存器q的内容


":let a=line('.')\r"在宏工作之前将当前行号存储在变量a中。\r对于告诉Vim按Enter并执行命令是必需的,有关类似特殊字符的列表,请参见:help expr-quote

. @q .q寄存器的当前内容与前一个字符串和下一个字符串连接在一起,

":if line('.')==a|exe 'norm @q'|endif\r"在行没有变化的情况下调用宏q

再次,要保存一些击键,您可以通过定义以下定制命令来自动执行该过程:

let @q = ":let a=line('.')\r" . @q . ":if line('.')==a|exe 'norm @q'|endif\r"


再次,您要做的就是将宏记录为通常(非递归),然后键入:RecursiveMacroOnLine q以使存储在寄存器q中的宏重新出现


合并两个命令

还可以调整:RecursiveMacro,使其涵盖2种情况:


无条件进行宏递归,
在当前行停留的条件下进行宏递归

为此,您可以传递第二个:RecursiveMacro的参数。后者将仅测试其值,并根据该值执行前两个命令之一。它会给出这样的内容:

command! -register RecursiveMacroOnLine let @<reg> = ":let a=line('.')\r" . @<reg> . ":if line('.')==a|exe 'norm @<reg>'|endif\r"


或(使用换行符/反斜线使其更具可读性):

command! -register -nargs=1 RecursiveMacro if <args> | let @<reg> .= "@<reg>" | else | let @<reg> = ":let a=line('.')\r" . @<reg> . ":if line('.')==a|exe 'norm @<reg>'|endif\r" | endif


与以前相同,只是这次您必须提供一个第二个参数:RecursiveMacro(由于-nargs=1属性)。
执行此新命令时,Vim会自动使用您提供的值扩展<args>
如果第二个参数非零/ true(if <args>),将执行该命令的第一个版本(无条件地使宏递归的命令),否则,如果该命令为零/ false,则将执行第二个版本(在保持当前行的条件下使宏递归的命令) 。

所以回到上一个示例,它会给出以下内容:

command! -register -nargs=1 RecursiveMacro
           \ if <args> |
           \     let @<reg> .= "@<reg>" |
           \ else |
           \     let @<reg> = ":let a = line('.')\r" .
           \                  @<reg> .
           \                  ":if line('.')==a | exe 'norm @<reg>' | endif\r" |
           \ endif




qq开始记录内部寄存器q中的宏


<C-a>递增光标下的数字

w将光标移至ne xt编号

q结束记录

:RecursiveMacro q 0使存储在寄存器q中的宏递归,但仅直到行尾为止(由于第二个参数0

3G将光标移动到任意行(例如3)

0@q从行的开头重播递归宏

它应该具有与以前相同的结果:

qq
<C-a>
w
q
:RecursiveMacro q 0
3G
0@q


但是这次您不必在录制宏时键入分散注意力的命令,您只需关注

在第5步中,如果您向命令传递了一个非零参数,也就是说,如果您键入:RecursiveMacro q 1而不是:RecursiveMacro q 0,则宏q将无条件地递归,这将提供以下缓冲区:

1 2 3 4
1 2 3 4
2 3 4 5
1 2 3 4


这次宏不会在第三行的末尾而是在缓冲区的末尾停止。


有关更多信息,请参见:

1 2 3 4
1 2 3 4
2 3 4 5
2 3 4 5


评论


只要宏不更改匹配项的位置(例如,位置),就可以使用位置列表在宏中进行搜索匹配。 :lv / \%3l \ d / g% qqqqq :lne @ qq @ q将增加第3行的所有数字。也许有办法降低此解决方案的脆弱性?

– djjcast
16年2月10日在7:45

@djjcast您可以将其发布为答案,我已经尝试过了,它确实很棒。只有一种我不理解的情况,当我在下面的行1 2 3 4 5 6 7 8 9 10中执行宏时,我得到2 3 4 5 6 7 8 9 10 12而不是2 3 4 5 6 7 8 9 10 11.我不知道为什么,也许我输错了什么。无论如何,它似乎比我的简单方法复杂得多,它涉及到正则表达式来描述宏应将光标移至何处,以及一个我从未见过的以这种方式使用过的位置列表。我很喜欢!

– saginaw
16 Feb 10'在8:07

@djjcast对不起,我刚刚明白了,问题出在我的正则表达式中,我应该使用\ d \ +来描述多个数字。

– saginaw
16年2月10日在8:19

@djjcast啊,现在我明白了您所说的宏不应该更改比赛位置的意思。但是我不知道如何解决这个问题。我唯一的想法是从宏内部更新位置列表,但我不习惯该位置列表,这对我来说太复杂了,非常抱歉。

– saginaw
16年2月10日在10:16

@saginaw以相反的顺序遍历比赛似乎可以在大多数情况下解决问题,因为似乎宏更改先前比赛的位置的可能性较小。因此,在:lv ...命令之后,:lla命令可用于跳至最后一个匹配项,而:lp命令可用于按相反顺序进行匹配。

– djjcast
16年2月10日在14:03

#2 楼

递归宏将在遇到失败的命令后立即停止。因此,要在一行的结尾处停止,您需要一个将在该行的结尾处失败的命令。

默认情况下,l命令就是这样的命令,因此您可以使用它停止递归宏。如果光标不在该行的末尾,则只需使用命令h将其向后移。

因此,使用与saginaw相同的示例宏:

qqqqq<c-a>lhw@qq


细分为:



qqq:清除q寄存器,

qq:开始记录q寄存器中的一个宏,

<c-a>:增加光标下方的数字,

lh:如果我们在行的末尾,则中止该宏。否则,什么也不做。

w:前进到行中的下一个单词。

@q:递归

q:停止记录。

然后可以使用与saginaw描述的相同的0@q命令运行宏。


* 'whichwrap'选项可让您定义将哪些移动键环绕到在一行的开头或结尾时显示下一行(请参阅:help 'whichwrap')。如果您在此选项中设置了l,则将破坏上述解决方案。

但是,很可能只使用三个默认的普通模式命令之一来前进一个字符( <Space>l<Right>),因此,如果您在l设置中包含'whichwrap',则可以从'whichwrap'选项中删除不需要的一项,例如对于<Space>

:set whichwrap-=s


然后可以使用l命令替换宏的第4步中的<Space>命令。

评论


还要注意,设置virtualedit = one会干扰使用l来检测行尾,尽管不如whichwrap = 1那样严重。

–凯文
19年7月22日在18:02

@凯文好点!我将更新答案以提及“ ve”

–丰富
19年7月23日在10:45