Fügen Sie in jede Zeile einer Auswahl oder einer Übereinstimmung eine inkrementelle Zahl ein

10

Ich habe ein Problem, bei dem ich mir zwei allgemeine Lösungsansätze vorstellen kann, aber ich kenne keine Einzelheiten für beide Ansätze.

...
Level 1:    cũng    also
Level 1:    và      and
Level 1:    như     like; such as
Level 2:    các     plural marker
Level 2:    của     belonging to
...

Für jede Zeile, die mit "Level n" beginnt, möchte ich eine Zahl einfügen, die mit "01" beginnt. Der Einfachheit halber stellen wir die Zahl voran.

Ansatz 1: Wählen Sie alle Linien mit derselben Ebene manuell aus. Rufe Magie an, die ich bald lernen werde.

Ansatz 2: Schreiben Sie eine Suche und ersetzen Sie sie, wobei alle Zeilen mit der angegebenen Ebene übereinstimmen, die bei jeder Übereinstimmung eine Zahl im Ersetzungstext enthält, die bei jeder Übereinstimmung um eins erhöht wird.

Ich habe ähnliche Fragen auf StackOverflow oder auf anderen Vim-Sites gefunden, aber jede scheint eines oder mehrere der folgenden Probleme zu haben:

  1. Hier geht es darum, die aktuelle Zeilennummer anstelle einer beliebigen, aber inkrementellen Nummer einzufügen.
  2. Füllt die Nummer nicht auf Null.
  3. Funktioniert nicht für Auswahlen auf meinem Vim 7.4 unter Windows 7. (Diese führen zu dem Fehler E481: No range allowed.)

Ich verwende gVim in Windows mit mswin.vim, aber eine Lösung, die auf allen Vanilla Vim-Installationen funktioniert, ohne das Setup anpassen zu müssen, ist möglicherweise am besten.

Hippietrail
quelle
Die besten Tags, die ich mir für die Frage vorstellen kann, gibt es nicht: Auswahl und Bereich. Sie können also eines dieser Tags erneut markieren oder erstellen.
Hippietrail
1
Dieses Plugin ist keine vollständige Lösung für Ihr Problem, aber es ist äußerst nützlich, um Spalten mit Zahlen hinzuzufügen : VisIncr . Docs hier . FWIW.
lcd047

Antworten:

17

Ähnlich wie bei der Antwort unter /vi//a/818/227 können Sie den globalen Befehl verwenden.

Damit können Sie vim anweisen, nach Linien zu suchen, die einem Muster entsprechen, und dann Befehle ausführen.

In Ihrem Fall möchten Sie Zeilen, die mit "Level N:" beginnen, Text voranstellen, damit unser globaler Befehl lautet

:g/^Level \d:/{COMMANDS}

Verwenden des Ersatzbefehls (Ersetzen regulärer Ausdrücke) für den Befehl

Die Befehle machen mehr Spaß. Normalerweise ersetze ich gerne reguläre Ausdrücke für solche Dinge, da es einfach ist, Variablen zu verwenden.

Beispiel für Ihre Frage

:let i = 1 | g/^Level \d:/s/^/\=printf("%02d ", i)/ | let i = i+1

Wie es funktioniert

Im Ersetzungsabschnitt eines Substitutionsbefehls kann ein Ausdruck stehen.

Als erstes setzen wir eine Variable ials Startnummer. Ich habe 1 gewählt, aber jede Zahl reicht aus.let i = 1

Dann führen wir unseren globalen Befehl aus, der uns dazu veranlasst, eine Aktion für übereinstimmende Zeilen auszuführen. g/^Level \d:/

Wir lassen unseren globalen Befehl den Wert einfügen und unseren Zähler mit dem Substitutionsbefehl und dem Befehl let erhöhen. s/^/\=printf("%02d ", i)/ | let i = i+1

Der reguläre Ausdruck des Substitutionsbefehls findet den Zeilenanfang ^und ersetzt ihn durch einen Ausdruck. Unser Ausdruck ist das Ergebnis eines formatierten Drucks. Wie in der C-Sprache verwendet vims printf Formatierungsparameter. %02dbedeutet, ein Argument so zu konvertieren, als wäre es eine Dezimalzahl d, die mindestens 2 Leerzeichen belegt 2und mit 0 auffüllt 0. Einzelheiten und andere Konvertierungsoptionen (einschließlich Gleitkommaformatierung) finden Sie unter :help printf. Wir geben printf unsere Zählvariable iund es gibt uns 01das erste Mal, 02das zweite Mal usw. Dies wird vom Substitutionsbefehl verwendet, um den Zeilenanfang zu ersetzen und das Ergebnis des printf am Anfang effektiv einzufügen.

Beachten Sie, dass ich nach dem d ein Leerzeichen setze : "%02d ". Sie haben in der Frage nicht danach gefragt (und ich habe keine Beispielausgabe gesehen), aber ich vermutete, dass Sie die Nummer vom Wort "Level" trennen wollten. Entfernen Sie das Leerzeichen aus der Zeichenfolge, die printf zugewiesen wurde, damit die eingefügte Nummer direkt neben dem L in Level angezeigt wird.

Schließlich let i = i + 1erhöht dies unseren Zähler nach jeder Ersetzung.

Dies kann allgemein angewendet werden, um Teile von Linien, die mit anderen Kriterien übereinstimmen, durch beliebige Funktionsdaten zu ersetzen.

Kombinierte normale Befehle verwenden

Dies ist gut für einfache Einfügungen oder komplexe Bearbeitungen. Wie beim Ersetzen verwenden wir global, um eine Übereinstimmung zu erzielen, aber anstelle der Ersetzung durch reguläre Ausdrücke führen wir eine Reihe von Operationen aus, als ob sie vom Benutzer eingegeben würden.

Beispiel für Ihre Frage

:let i = 1 | g/^Level \d:/execute "normal! I" . printf("%02d ", i) | let i = i+1

Wie es funktioniert

Die verwendeten Werte sind dem Ersatz sehr ähnlich (wir verwenden immer noch printf, um unsere Zahl so zu formatieren, dass sie mit 2 Ziffern auf 0 aufgefüllt wird), aber die Operation ist anders.

Hier verwenden wir den Befehl execute, der eine Zeichenfolge verwendet und die Zeichenfolge als ex-Befehl ausführt ( :help :exe). Wir konstruieren einen String, der "normal! I" mit unseren Daten kombiniert, der beim ersten Mal "normal! I01" und beim zweiten Mal "normal! I02" usw. ist.

Der normalBefehl führt Operationen wie im normalen Modus aus. In diesem Beispiel lautet unser normaler Befehl Iam Anfang der Zeile. Wenn wir es verwendet ddhätten, owürde es die Zeile löschen, würde eine neue Zeile nach der übereinstimmenden Zeile öffnen. Es ist, als hätten Sie selbst I(oder andere Vorgänge) im normalen Modus getippt . Wir verwenden das !After normal, um sicherzustellen, dass keine Zuordnungen im Weg sind. Siehe :help :normal.

Was dann eingefügt wird, ist der Wert unseres printf, wie im ersten Beispiel für die Verwendung von Ersatz.

Diese Methode kann ausgefallener sein als Regex, da Sie Dinge wie tun können execute "normal! ^2wy" . i . "th$p", die an den Anfang des Textes gehen ^, 2 Wörter vorwärts bewegen 2w, bis zum i-ten 'h'-Zeichen y" . i . "thziehen, bis zum Ende der Zeile gehen $und einfügen p.

Dies ist fast so, als würde man ein Makro ausführen, verbraucht jedoch kein Register und kann Zeichenfolgen aus beliebigen Ausdrücken kombinieren. Ich finde das sehr mächtig.

Nähere dich, wo jedes Level seinen eigenen Zähler hat

Vielleicht möchten Sie, dass jedes Level seinen eigenen Zähler erhält. Wenn Sie die maximale Anzahl von Ebenen im Voraus kennen, können Sie Folgendes tun (das Hinzufügen von zusätzlichem Code, um die größte Ebene zu finden, ist möglicherweise nicht allzu schwierig, würde diese Antwort jedoch zu lang machen. Dies wird immer länger).

Lassen Sie zunächst i frei, falls wir es bereits als Ganzzahl verwendet haben. Wir können i nicht in eine Liste konvertieren, wir müssen es so erstellen.

:unlet! i

Als nächstes setzen wir i als eine Liste, die die Anzahl der Ebenen enthält. Sie haben 2 in Ihrer Frage gezeigt, aber nehmen wir zum Spaß 10 an. Da die Listenindizierung auf 0 basiert und ich mich nicht darum kümmern möchte, 1 basierend auf Ihrer Liste zu korrigieren, erstellen wir einfach genug Elemente (11) und verwenden niemals den 0-Index.

:let j = 0
:let i = []
:while j < 11 | let i += [1] | let j += 1 | endwhile

Als nächstes brauchen wir einen Weg, um die Levelnummer zu erhalten. Glücklicherweise ist Ersatz auch als Funktion verfügbar, daher geben wir ihm unsere Zeile und extrahieren die Ebenennummersubstitute(getline("."), "^Level \\(\\d\\):.*", "\\=submatch(1)", "")

Da i jetzt eine Liste von 11 1s ist (jeder Index ist der Zähler für unser Niveau), können wir jetzt eines der obigen Beispiele anpassen, um das Ergebnis dieser Substitution zu verwenden:

Über Ersatzbefehl:

:unlet! i | unlet! j | let j = 0 | let i = [] | while j < 11 | let i += [1] | let j += 1 | endwhile
:g/^Level \d:/let ind=str2nr(substitute(getline("."), "^Level \\(\\d\\):.*", "\\=submatch(1)", "")) | s/^/\=printf("%02d ", i[ind])/ | let i[ind] += 1

Über normalen Befehl:

:unlet! i | unlet! j | let j = 0 | let i = [] | while j < 11 | let i += [1] | let j += 1 | endwhile
:g/^Level \d:/let ind=str2nr(substitute(getline("."), "^Level \\(\\d\\):.*", "\\=submatch(1)", "")) | execute "normal! I" . printf("%02d ", i[ind]) | let i[ind] += 1

Beispieleingabe:

Level 1: stuff

Level 1: Stuff

Some text
Level 3: Other

Level 1: Meh

Level 2: More

Beispielausgabe:

01 Level 1: stuff

02 Level 1: Stuff

Some text
01 Level 3: Other

03 Level 1: Meh

01 Level 2: More
John O'M.
quelle
Wow, sehr enzyklopädisch. Ich habe nur den ersten Teil gelesen und das Gefühl, dass ich dadurch mehr über Vim gelernt habe als in den letzten Jahren. Dies ist die Art von Antwort, die Stack Exchange besser macht als durchschnittliche Q & A-Sites und auch die Vorteile einer Vim-spezifischen SE zeigt.
Hippietrail
3

Sie können von /programming//a/4224454/15934 bis Null Ihre Zahlen auffüllen.

" A reminder: how to start numbers at the first line
:'<,'>s/^\s*\zs/\=(line('.') - line("'<")+1).'. '

Um das Auffüllen von Zahlen zu vereinfachen, würde ich zwei Funktionen und einen Befehl verwenden:

command!  -range=% -nargs=? PrependNumbers <line1>,<line2>call s:Prepend(<args>)

function! s:ReplExpr(nb_digits, number)
  return repeat('0', a:nb_digits - strlen(a:number)).a:number
endfunction

function! s:Prepend(...) range
  let pattern = a:0 > 0 ? '\ze'. a:1 : '^'
  let nb_values = (a:lastline - a:firstline) + 1
  let nb_digits = strlen(nb_values)
  exe ':'.a:firstline.','a:lastline.'s#'.pattern.'#\=s:ReplExpr(nb_digits, 1+ line(".")-'.a:firstline.')." "#'
endfunction

Wählen Sie dann Ihre Zeilen aus und geben :PrependNumberSie ein (Sie werden sehen :'<,'>PrependNumber. [Hinweis: Der Befehl verwendet einen optionalen Parameter: das Muster, vor dem die Zahl eingefügt wird]

Luc Hermitte
quelle
Aber heißt das nicht line(".")"aktuelle Zeilennummer verwenden"? Das war mein Problem 1. mit früheren Antworten konnte ich finden.
Hippietrail
1
Ja ist es. Deshalb subtrahiere ich die Zeilennummer der ersten Zeile im Bereich.
Luc Hermitte
Ah OK, ich habe es noch nicht getestet, weil ich mich nicht einmal daran erinnern kann, wie man ein Skript in Vim
eingibt.
1
Kopieren Sie den Code unter Windows und fügen Sie ihn ein $HOME/vimfiles/plugin/whatevernameyouwish.vim. Oder sogar Ihren $HOME/_vimrc(Windows-Dateinamen), wenn Sie es vorziehen (beim ersten Mal). Wenn Sie nicht sicher sind, was $ HOME auf Ihrem Computer ist, fragen Sie vim, was es denkt ->:echo $HOME
Luc Hermitte
1
Sie brauchen nicht einmal, :sourcewenn das vim-Skript korrekt im richtigen Verzeichnis installiert ist ({rtp} / plugin oder {rtp} / ftplugin / {filetype} / ftplugin für filetypspezifische Plugins). :sourceist das, womit wir vor mehr als anderthalb Jahrzehnten spielen mussten.
Luc Hermitte
2

Sie können sich dem nähern, indem Sie ein Makro aufzeichnen. Beginnen Sie mit Ihrer Originaldatei und stellen Sie der ersten Instanz die Nummer voran.

01 Level 1:    cũng    also
Level 1:    và      and
Level 1:    như     like; such as
Level 2:    các     plural marker
Level 2:    của     belonging to

Bewegen Sie den Cursor auf 01, wählen Sie ihn aus und ziehen Sie ihn mit yiw. Jetzt möchten Sie Ihre Aktionen von diesem Punkt aus aufzeichnen.

qq/^Level 1<CR>P<C-A>A<space><esc>0yiwq

  • qq Starten Sie ein Makro in Register q
  • /^Level 1<CR> Suchen Sie nach einer Zeile, die mit "Level 1" beginnt.
  • P vor dem Cursor einfügen (enthält Ihre Nummer)
  • <C-A> erhöht die Nummer
  • A<space><esc> Fügen Sie nach der Nummer ein Leerzeichen ein
  • 0 Gehen Sie zum Start
  • yiw Ziehen Sie die aktuelle Nummer
  • q Beenden Sie das Makro

Wiederholen Sie dieses Makro dann mit @q.

jecxjo
quelle
1
Ist <C-A>Kontrolle + a? Damit wird alles in Vim unter Windows ausgewählt.
Hippietrail
Es ist Kontrolle + a. In vim sollte es eine Zahl erhöhen. Wenn Sie Ihre Tastenkombinationen geändert haben, um Windows-Aktionen anstelle von Vim-Aktionen auszuführen, können Sie die meisten Dinge, die hier veröffentlicht werden, nicht ausführen.
Jecxjo
Nein, die Vim-Version für Windows enthält aufgrund der unendlichen Weisheit eines Menschen unterschiedliche Bindungen. Ich verwende nur die Vanille-Out-of-the-Box-Bindungen. Ich habe in über zwanzig Jahren nie eine Vim-Schlüsselbindung geändert, an die ich mich erinnern kann (-:
Hippietrail
Sollte nicht der Fall sein, hat meine Windows-Installation normale VIM-Bindungen. Könnte es möglich sein, dass Sie den Build von vim eines anderen installiert haben? Oder könnten Sie vim im "Easy" -Modus ausführen? Ich glaube, Windows installiert Desktop-Symbole mit normalen und einfachen Modusoptionen.
Jecxjo
3
Nein, weil die Standardinstallation in Windows mswin.vim lädt. Wenn Sie Ihr eigenes vimrc erstellen und mswin.vim nicht laden, erhalten Sie die normalen vim-Bindungen. Ich behalte für alle meine Installationen (Linux, Mac und Windows) ein einziges vimrc und beschäftige mich nie mit mswin.vim. Weitere Informationen zu diesem Problem finden Sie unter stackoverflow.com/questions/289681/…
jecxjo