Answers:
可能有一种更简单的方法,但是您可以尝试以下方法。
假设您将使用寄存器q
来记录您的递归宏。
在记录的开头,键入:
:let a = line('.')
然后,在记录的最后,而不是单击@q
以使宏递归,请键入以下命令:
:if line('.') == a | exe 'norm @q' | endif
最后使用结束宏的录制q
。
您输入的最后一条命令将重播宏q
(exe '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
qqq
清除寄存器的内容,q
以便在宏定义期间最初调用它时不会干扰qq
开始录音:let a=line('.')
在变量中存储当前行号 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
递归。
您可以执行相同的操作以使宏在当前行中保留的条件下递归:
let @q = ":let a=line('.')\r" . @q . ":if line('.')==a|exe 'norm @q'|endif\r"
这与帖子开头描述的完全相同,只是这次您在录制后进行了此操作。您只需连接两个字符串,一个在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
在行没有变化的情况下调用宏同样,要保存一些击键,您可以通过定义以下定制命令来自动执行该过程:
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
qq
开始记录寄存器内部的宏 q
<C-a>
增加光标下的数字w
将光标移到下一个数字q
结束录音:RecursiveMacro q 0
使存储在寄存器中的宏q
递归,但仅到行尾为止(由于第二个参数0
)3G
将光标移动到任意行(例如3)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
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
。我不知道为什么,也许我输错了什么。无论如何,它似乎比我的简单方法复杂得多,它涉及到正则表达式来描述宏应将光标移至何处,以及一个我从未见过的以这种方式使用过的位置列表。我很喜欢!
\d\+
描述多个数字。
:lv ...
命令之后,该:lla
命令可用于跳至最后一场比赛,而该:lp
命令可用于以相反的顺序进行比赛。
递归宏将在遇到失败的命令后立即停止。因此,要在一行的末尾停止,您需要一个在该行的末尾将失败的命令。
默认情况下*,该l
命令就是这样的命令,因此您可以使用它来停止递归宏。如果光标不在该行的末尾,则只需要使用命令之后将其移回即可h
。
因此,使用与saginaw相同的示例宏:
qqqqq<c-a>lhw@qq
细分:
qqq
:清除q寄存器,qq
:开始在q
寄存器中记录宏,<c-a>
:增加光标下方的数字,lh
:如果我们在行尾,请中止该宏。否则,什么都不做。w
:前进到该行的下一个单词。@q
:递归q
:停止录制。然后,可以使用与0@q
saginaw描述的命令相同的命令运行宏。
*该'whichwrap'
选项使您可以定义当您位于一行的开头或结尾时,哪些移动键将环绕到下一行(请参阅参考资料:help 'whichwrap'
)。如果已l
设置此选项,则它将破坏上述解决方案。
然而,很可能你只使用三个默认的普通模式命令的一个推进一个字符(<Space>
,l
,和<Right>
),因此,如果您已l
包含在您的'whichwrap'
设置,您可以删除一个你不从使用'whichwrap'
选项,例如<Space>
:
:set whichwrap-=s
然后,您可以用l
命令替换宏步骤4中的<Space>
命令。
virtualedit=onemore
影响设置l
用于检测行尾whichwrap=l
。
've'
:lv /\%3l\d/g %<CR>qqqqq<C-a>:lne<CR>@qq@q
将增加第3行上的所有数字。也许有办法使该解决方案不那么脆弱?