如何在行尾停止递归宏?


13

如何创建一个递归宏,使其仅运行到行尾?

或者如何只在行尾运行递归宏?

Answers:


11

可能有一种更简单的方法,但是您可以尝试以下方法。

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

在记录的开头,键入:

:let a = line('.')

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

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

最后使用结束宏的录制q

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

:normal命令允许您@q从Ex模式键入普通命令(例如)。
而将命令包装到字符串中并由命令执行的原因: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
  1. qqq清除寄存器的内容,q以便在宏定义期间最初调用它时不会干扰
  2. qq 开始录音
  3. :let a=line('.') 在变量中存储当前行号 a
  4. Ctrl+ a增加光标下方的数字
  5. w 将光标移到下一个数字
  6. :if line('.')==a|exe 'norm @q'|endif 仅在行号未更改的情况下调用宏
  7. 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递归。


您可以执行相同的操作以使宏在当前行中保留的条件下递归:

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

这与帖子开头描述的完全相同,只是这次您在录制后进行了此操作。您只需连接两个字符串,一个在q寄存器当前包含的击键之前,一个在之后:

  1. let @q = 重新定义寄存器的内容 q
  2. ":let a=line('.')\r"a在宏执行其工作之前
    \r必须将当前行号存储在变量中,以告诉Vim按下Enter并执行命令,:help expr-quote有关类似特殊字符的列表 ,请参见
  3. . @q .q寄存器的当前内容与上一个字符串和下一个字符串连接起来,
  4. ":if line('.')==a|exe 'norm @q'|endif\r"q在行没有变化的情况下调用宏

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

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

同样,您要做的就是照常记录您的宏(非递归),然后键入:RecursiveMacroOnLine q以使存储在寄存器中的宏q在当前行的情况下递归存储。


合并2个命令

您也可以进行调整:RecursiveMacro,使其涵盖2种情况:

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

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

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

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

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,则将执行第二个版本(使宏递归条件(它停留在当前行)。

因此,回到前面的示例,它将得到以下结果:

qq
<C-a>
w
q
:RecursiveMacro q 0
3G
0@q
  1. qq 开始记录寄存器内部的宏 q
  2. <C-a> 增加光标下的数字
  3. w 将光标移到下一个数字
  4. q 结束录音
  5. :RecursiveMacro q 0使存储在寄存器中的宏q 递归,但仅到行尾为止(由于第二个参数0
  6. 3G 将光标移动到任意行(例如3)
  7. 0@q 从行的开头重播递归宏

它应产生与以前相同的结果:

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

但是这一次,您不必在录制宏的过程中输入分散注意力的命令,您只需专注于制作一个有效的命令即可。

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

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

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


有关更多信息,请参见:

:help line()
:help :normal
:help :execute
:help :command-nargs
:help :command-register

2
只要宏不改变匹配的位置,位置列表就可以用于推进搜索匹配,例如,:lv /\%3l\d/g %<CR>qqqqq<C-a>:lne<CR>@qq@q将增加第3行上的所有数字。也许有办法使该解决方案不那么脆弱?
djjcast

@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

@djjcast对不起,我刚刚明白了,问题仅出自我的正则表达式,我本该用来\d\+描述多个数字。
saginaw '16

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

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

9

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

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

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

qqqqq<c-a>lhw@qq

细分:

  1. qqq:清除q寄存器,
  2. qq:开始在q寄存器中记录宏,
  3. <c-a>:增加光标下方的数字,
  4. lh:如果我们在行尾,请中止该宏。否则,什么都不做。
  5. w:前进到该行的下一个单词。
  6. @q:递归
  7. q:停止录制。

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


*该'whichwrap'选项使您可以定义当您位于一行的开头或结尾时,哪些移动键将环绕到下一行(请参阅参考资料:help 'whichwrap')。如果已l设置此选项,则它将破坏上述解决方案。

然而,很可能你只使用三个默认的普通模式命令的一个推进一个字符(<Space>l,和<Right>),因此,如果您已l包含在您的'whichwrap'设置,您可以删除一个你从使用'whichwrap'选项,例如<Space>

:set whichwrap-=s

然后,您可以用l命令替换宏步骤4中的<Space>命令。


1
还请注意,设置不会像那样严重virtualedit=onemore影响设置l用于检测行尾whichwrap=l
凯文(Kevin)

@凯文好点!我将更新我的回答以提及've'
Rich
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.