Как я могу создать рекурсивный макрос, чтобы он работал только до конца строки?
Или как запустить рекурсивный макрос только до конца строки?
Вероятно, есть более простой способ, но, возможно, вы могли бы попробовать следующее.
Допустим, вы будете использовать register 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"
.=
это оператор, который позволяет добавить строку к другой.
Это должно добавить 2 символа @q
в самом конце последовательности нажатий клавиш, хранящихся в регистре 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 нажать клавишу 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
. Последний будет просто проверять его значение и, в зависимости от значения, будет выполнять одну из 2 предыдущих команд. Это даст что-то вроде этого:
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>
на указанное вами значение.
Если этот 2-й аргумент не равен нулю / 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
На этот раз макрос остановился бы не в конце 3-й строки, а в конце буфера.
Для получения дополнительной информации см .:
:help line()
:help :normal
:help :execute
:help :command-nargs
:help :command-register
:lv /\%3l\d/g %<CR>qqqqq<C-a>:lne<CR>@qq@q
, увеличивает все числа в строке 3. Может быть, есть способ сделать это решение менее хрупким?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 :
Сломано:
qqq
: Очистить регистр q,qq
: Начать запись макроса вq
регистр,<c-a>
: Увеличить число под курсором,lh
: Если мы находимся в конце строки, отменим макрос. В противном случае ничего не делать.w
: Переход к следующему слову в строке.@q
: Рекурсq
: Остановить запись.Затем вы можете запустить макрос с помощью той же
0@q
команды, как описано в saginaw.*
'whichwrap'
Опция позволяет вам определить, какие клавиши перемещения будут переходить на следующую строку, когда вы находитесь в начале или конце строки (см.:help 'whichwrap'
). Если выl
установили эту опцию, это нарушит решение, описанное выше.Однако, вполне вероятно , что вы используете только одну из команд нормального режима на три по умолчанию для продвижения одного символа (
<Space>
,l
и<Right>
), так что если выl
включены в'whichwrap'
настройках, вы можете удалить тот , который вы не используете из'whichwrap'
вариант, например, для<Space>
:Затем вы можете заменить
l
команду на шаге 4 макроса<Space>
командой.источник
virtualedit=onemore
будет мешать использованиюl
для обнаружения конца строки, хотя и не так сильно, какwhichwrap=l
.'ve'