Ответы:
Вероятно, есть более простой способ, но, возможно, вы могли бы попробовать следующее.
Допустим, вы будете использовать 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
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. Может быть, есть способ сделать это решение менее хрупким?