Как мне остановить рекурсивный макрос в конце строки?

13

Как я могу создать рекурсивный макрос, чтобы он работал только до конца строки?

Или как запустить рекурсивный макрос только до конца строки?

DinushanM
источник

Ответы:

11

Вероятно, есть более простой способ, но, возможно, вы могли бы попробовать следующее.

Допустим, вы будете использовать 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
  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"
.=это оператор, который позволяет добавить строку к другой.

Это должно добавить 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содержащихся в регистре:

  1. let @q = переопределяет содержимое реестра q
  2. ":let a=line('.')\r"сохраняет текущий номер строки внутри переменной aдо того, как макрос
    \rвыполнит свою работу , необходимо, чтобы Vim сказал Enter нажать клавишу 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. Последний будет просто проверять его значение и, в зависимости от значения, будет выполнять одну из 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
  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

На этот раз макрос остановился бы не в конце 3-й строки, а в конце буфера.


Для получения дополнительной информации см .:

: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
@djjcast Извините, я только что понял, проблема возникла просто из моего регулярного выражения, я должен был использовать \d\+для описания многозначных чисел.
сагино
@djjcast А теперь я понимаю, что вы имели в виду, когда говорили, что макрос не должен менять положение совпадений. Но я не знаю, как решить эту проблему. Единственная идея, которая у меня есть, - обновить список расположений внутри макроса, но я не привык к списку расположений, он слишком сложен для меня, извините.
saginaw
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@qкоманды, как описано в saginaw.


* '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'
Рич