Wie halte ich ein rekursives Makro am Ende der Zeile an?

13

Wie kann ich ein rekursives Makro erstellen, sodass es nur bis zum Zeilenende ausgeführt wird?

Oder wie man ein rekursives Makro nur bis zum Ende der Zeile ausführt?

DinushanM
quelle

Antworten:

11

Es gibt wahrscheinlich eine einfachere Methode, aber vielleicht können Sie Folgendes versuchen.

Angenommen, Sie verwenden register q, um Ihr rekursives Makro aufzuzeichnen.

Geben Sie zu Beginn der Aufnahme Folgendes ein:

:let a = line('.')

Geben Sie am Ende der Aufzeichnung @qden folgenden Befehl ein , anstatt das Makro rekursiv zu machen:

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

Beenden Sie abschließend die Aufzeichnung des Makros mit q.

Der zuletzt eingegebene Befehl gibt das Makro q( exe 'norm @q') nur dann wieder, wenn die aktuelle Zeilennummer ( line('.')) mit der ursprünglich in Variable gespeicherten übereinstimmt a.

Mit diesem :normalBefehl können Sie normale Befehle (wie @q) aus dem Ex-Modus eingeben .
Der Grund, warum der Befehl in eine Zeichenfolge eingeschlossen und vom Befehl ausgeführt wird, :executebesteht darin, zu verhindern, dass :normalder Rest des Befehls ( |endif) verbraucht (eingegeben ) wird.


Anwendungsbeispiel.

Angenommen, Sie haben den folgenden Puffer:

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

Und Sie möchten mit einem rekursiven Makro alle Zahlen einer beliebigen Zeile inkrementieren.

Sie können eingeben 0, um den Cursor an den Anfang einer Zeile zu bewegen, und dann die Aufzeichnung des Makros starten:

qqq
qq
:let a=line('.')
<C-a>
w
:if line('.')==a|exe 'norm @q'|endif
q
  1. qqqLöscht den Inhalt des Registers, qdamit er beim ersten Aufruf während der Definition des Makros nicht beeinträchtigt wird
  2. qq startet die Aufnahme
  3. :let a=line('.') speichert die aktuelle Zeilennummer in der Variablen a
  4. Ctrl+ aerhöht die Zahl unter dem Cursor
  5. w bewegt den Cursor zur nächsten Ziffer
  6. :if line('.')==a|exe 'norm @q'|endif ruft das Makro nur dann auf, wenn sich die Zeilennummer nicht geändert hat
  7. q stoppt die Aufnahme

Wenn Sie Ihr Makro definiert haben und den Cursor in der dritten Zeile positionieren, drücken Sie 0, um es an den Zeilenanfang zu verschieben, und @qdrücken Sie dann, um das Makro erneut abzuspielen. Dies qsollte sich nur auf die aktuelle Zeile und nicht auf die anderen Zeilen auswirken:

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

Machen Sie ein Makro nach der Aufnahme rekursiv

Wenn Sie möchten, können Sie Ihr Makro nach der Aufzeichnung rekursiv machen, indem Sie die Zeichenfolge in einem Register speichern und zwei Zeichenfolgen mit dem Punktoperator .verknüpfen.

Dies würde Ihnen mehrere Vorteile bringen:

  • Es ist nicht erforderlich, das Register vor der Aufnahme zu löschen, da die Zeichen @qim Makro hinzugefügt werden, nachdem sie definiert wurden und der alte Inhalt überschrieben wurde
  • Sie müssen während der Aufnahme nichts Ungewöhnliches eingeben, sondern können sich auf die Erstellung eines einfachen, funktionierenden Makros konzentrieren
  • Möglichkeit, es zu testen, bevor es rekursiv gemacht wird, um zu sehen, wie es sich verhält

Wenn Sie Ihr Makro wie gewohnt (nicht rekursiv) aufzeichnen, können Sie es anschließend mit dem folgenden Befehl rekursiv machen:

let @q = @q . "@q"

Oder noch kürzer: let @q .= "@q"
.=ist ein Operator, mit dem eine Zeichenfolge an eine andere angehängt werden kann.

Dies sollte die 2 Zeichen ganz @qam Ende der im Register gespeicherten Folge von Tastenanschlägen hinzufügen q. Sie können auch einen benutzerdefinierten Befehl definieren:

command! -register RecursiveMacro let @<reg> .= "@<reg>"

Es definiert den Befehl, :RecursiveMacroder auf den Namen eines Registers als Argument wartet (aufgrund des -registerAttributs, an das übergeben wird :command).
Es ist der gleiche Befehl wie zuvor, der einzige Unterschied ist, dass Sie jedes Vorkommen von qdurch ersetzen <reg>. Wenn der Befehl ausgeführt wird, erweitert Vim automatisch jedes Vorkommen um den von <reg>Ihnen angegebenen Registernamen.

Jetzt müssen Sie nur noch Ihr Makro wie gewohnt (nicht rekursiv) aufzeichnen und dann eingeben :RecursiveMacro q, um das im Register gespeicherte Makro qrekursiv zu machen.


Sie können das Gleiche tun, um ein Makro unter der Bedingung rekursiv zu machen, dass es in der aktuellen Zeile bleibt:

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

Es ist genau dasselbe, was zu Beginn des Beitrags beschrieben wurde, außer, dass Sie es dieses Mal nach der Aufnahme tun. Sie verketten nur zwei Zeichenfolgen, eine vor und eine nach den Tastenanschlägen, die das qRegister derzeit enthält:

  1. let @q = definiert den Inhalt des Registers neu q
  2. ":let a=line('.')\r"Speichert die aktuelle Zeilennummer in der Variablen, abevor das Makro seine Arbeit
    \rausführt. Dies ist erforderlich, um Vim anzuweisen, die Eingabetaste zu drücken und den Befehl auszuführen. :help expr-quoteEine Liste ähnlicher Sonderzeichen finden Sie unter.
  3. . @q .verkettet den aktuellen Inhalt des qRegisters mit dem vorherigen und dem nächsten String,
  4. ":if line('.')==a|exe 'norm @q'|endif\r"ruft das Makro qunter der Bedingung auf, dass sich die Zeile nicht geändert hat

Um einige Tastenanschläge zu speichern, können Sie den Prozess durch Definieren des folgenden benutzerdefinierten Befehls automatisieren:

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

Alles, was Sie tun müssen, ist, Ihr Makro wie gewohnt aufzuzeichnen (nicht rekursiv) und dann zu tippen :RecursiveMacroOnLine q, um das im Register gespeicherte Makro qrekursiv zu machen, sofern es in der aktuellen Zeile verbleibt.


Füge die 2 Befehle zusammen

Sie können auch :RecursiveMacroso einstellen , dass die 2 Fälle abgedeckt werden:

  • ein Makro unbedingt rekursiv machen,
  • mach ein Makro rekursiv unter der Bedingung, dass es in der aktuellen Zeile bleibt

Dazu können Sie ein zweites Argument an übergeben :RecursiveMacro. Letzterer testet einfach seinen Wert und führt je nach Wert einen der beiden vorherigen Befehle aus. Es würde so etwas geben:

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

Oder (mit Zeilenfortsätzen / Backslashes, um die Lesbarkeit zu verbessern):

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

Es ist dasselbe wie zuvor, außer dass Sie diesmal ein zweites Argument für :RecursiveMacro(wegen des -nargs=1Attributs) angeben müssen.
Wenn dieser neue Befehl ausgeführt wird, wird Vim automatisch um <args>den von Ihnen angegebenen Wert erweitert .
Wenn dieses zweite Argument nicht null / wahr ist ( if <args>), wird die erste Version des Befehls ausgeführt (diejenige, die ein Makro bedingungslos rekursiv macht), andernfalls, wenn es null / falsch ist, wird die zweite Version ausgeführt (diejenige, die es erstellt) ein Makro rekursiv unter der Bedingung, dass es in der aktuellen Zeile bleibt).

Wenn Sie also zum vorherigen Beispiel zurückkehren, erhalten Sie Folgendes:

qq
<C-a>
w
q
:RecursiveMacro q 0
3G
0@q
  1. qq Startet die Aufzeichnung eines Makros im Register q
  2. <C-a> erhöht die Zahl unter dem Cursor
  3. w bewegt den Cursor zur nächsten Ziffer
  4. q Beendet die Aufnahme
  5. :RecursiveMacro q 0macht das im Register gespeicherte Makro q rekursiv, jedoch nur bis zum Ende der Zeile (aufgrund des zweiten Arguments 0)
  6. 3G bewegt den Cursor auf eine beliebige Zeile (3 zum Beispiel)
  7. 0@q Spielt das rekursive Makro ab dem Zeilenanfang ab

Es sollte das gleiche Ergebnis wie zuvor geben:

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

Aber dieses Mal mussten Sie die Ablenkungsbefehle während der Aufzeichnung Ihres Makros nicht eingeben, sondern konnten sich einfach darauf konzentrieren, ein funktionierendes zu erstellen.

Wenn Sie in Schritt 5 ein Argument ungleich Null an den Befehl übergeben hätten, dh wenn Sie :RecursiveMacro q 1stattdessen eingegeben hätten :RecursiveMacro q 0, wäre das Makro qbedingungslos rekursiv geworden, was den folgenden Puffer ergeben hätte:

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

Dieses Mal hätte das Makro nicht am Ende der dritten Zeile, sondern am Ende des Puffers angehalten.


Weitere Informationen finden Sie unter:

:help line()
:help :normal
:help :execute
:help :command-nargs
:help :command-register
saginaw
quelle
2
Die Standortliste kann verwendet werden, um Suchtreffer in einem Makro zu überspringen, solange das Makro die Position der Treffer nicht ändert, z. B. :lv /\%3l\d/g %<CR>qqqqq<C-a>:lne<CR>@qq@qwerden alle Zahlen in Zeile 3 erhöht. Vielleicht gibt es eine Möglichkeit, diese Lösung weniger anfällig zu machen?
DJJCAST
@djjcast Du könntest es als Antwort posten, ich habe es ausprobiert und es funktioniert wirklich toll. Es gibt nur einen Fall, den ich nicht verstehe: Wenn ich das Makro in der folgenden Zeile ausführe 1 2 3 4 5 6 7 8 9 10, erhalte ich 2 3 4 5 6 7 8 9 10 12anstelle von 2 3 4 5 6 7 8 9 10 11. Ich weiß nicht warum, vielleicht habe ich etwas falsch geschrieben. Auf jeden Fall scheint es ausgefeilter zu sein als mein einfacher Ansatz, und es beinhaltet Regexes, um zu beschreiben, wohin das Makro den Cursor bewegen soll, sowie eine Standortliste, die ich auf diese Weise noch nie gesehen habe. Ich mag es sehr!
Saginaw
@djjcast Sorry, ich habe gerade verstanden, das Problem kam einfach aus meiner Regex, ich hätte \d\+zur Beschreibung mehrstelliger Zahlen verwenden sollen.
Saginaw
@djjcast Ah, jetzt verstehe ich, was Sie meinten, als Sie sagten, das Makro sollte die Position der Übereinstimmungen nicht ändern. Aber ich weiß nicht, wie ich dieses Problem lösen soll. Die einzige Idee, die ich hätte, wäre, die Standortliste aus dem Makro heraus zu aktualisieren, aber ich bin nicht an die Standortliste gewöhnt, sie ist zu komplex für mich, wirklich leid.
Saginaw
1
@saginaw Das Durchlaufen der Übereinstimmungen in umgekehrter Reihenfolge scheint das Problem in den meisten Fällen zu beheben, da es weniger wahrscheinlich ist, dass ein Makro die Positionen früherer Übereinstimmungen ändert. Nach dem :lv ...Befehl kann also mit dem :llaBefehl zum letzten Treffer gesprungen werden, und mit dem :lpBefehl können die Treffer in umgekehrter Reihenfolge durchlaufen werden.
DJJCAST
9

Ein rekursives Makro stoppt, sobald es auf einen fehlgeschlagenen Befehl stößt. Um am Ende einer Zeile anzuhalten, benötigen Sie daher einen Befehl, der am Ende der Zeile fehlschlägt.

Standardmäßig * ist der lBefehl ein solcher Befehl, sodass Sie ihn zum Stoppen eines rekursiven Makros verwenden können. Befindet sich der Cursor nicht am Ende der Zeile, müssen Sie ihn nur mit dem Befehl zurücksetzen h.

Verwenden Sie also dasselbe Beispielmakro wie saginaw :

qqqqq<c-a>lhw@qq

Heruntergebrochen:

  1. qqq: Löschen Sie das q-Register,
  2. qq: Starten Sie die Aufzeichnung eines Makros im qRegister.
  3. <c-a>: Erhöhe die Zahl unter dem Cursor,
  4. lh: Wenn wir am Ende der Zeile sind, brechen Sie das Makro ab. Sonst nichts tun.
  5. w: Weiter zum nächsten Wort in der Zeile.
  6. @q: Recurse
  7. q: Höre auf, aufzunehmen.

Sie können das Makro dann mit demselben 0@qBefehl ausführen, der von saginaw beschrieben wurde.


* Mit dieser 'whichwrap'Option können Sie festlegen, welche Bewegungstasten am Anfang oder Ende einer Zeile in die nächste Zeile übergehen sollen (siehe :help 'whichwrap'). Wenn Sie ldiese Option aktiviert haben, wird die oben beschriebene Lösung nicht mehr unterstützt.

Es ist jedoch wahrscheinlich, dass Sie nur einen der drei Standardbefehle für den Normalmodus zum Vorrücken eines einzelnen Zeichens ( <Space>,, lund <Right>) verwenden. Wenn Sie also lin Ihre 'whichwrap'Einstellung aufgenommen haben, können Sie einen Befehl entfernen, den Sie nicht verwenden 'whichwrap'Option, zB für <Space>:

:set whichwrap-=s

Anschließend können Sie den lBefehl in Schritt 4 des Makros durch einen <Space>Befehl ersetzen .

Reich
quelle
1
Beachten Sie auch, dass die Einstellung virtualedit=onemoredie Verwendung lvon stört , um das Zeilenende zu erkennen, wenn auch nicht so stark wie whichwrap=l.
Kevin
@ Kevin Guter Punkt! Ich werde meine Antwort aktualisieren, um zu erwähnen've'
Rich