Vim: globales Suchen und Ersetzen ausgehend von der Cursorposition

112

Wenn ich mit suche

/\vSEARCHTERM

Vim startet die Suche von der Cursorposition nach unten und wird nach oben gewickelt. Allerdings beim Suchen und Ersetzen mit

:%s/\vBEFORE/AFTER/gc

Vim beginnt oben in der Datei.

Gibt es eine Möglichkeit Vim zu machen perform Suchen und Ersetzen von der Cursorposition und nach oben Umwickeln einmal das Ende erreicht?

basteln
quelle
2
\vpattern- 'sehr magisches' Muster: Nicht-alphanumerische Zeichen werden als spezielle Regex-Symbole interpretiert (kein Entkommen erforderlich)
Carlos Araya
Was ist der Punkt, um von der aktuellen Position aus zu beginnen und die Datei zu umbrechen, anstatt nur zu verwenden %? Ich sehe keinen vernünftigen Nutzen dafür, dass Sie sowieso die ganze Datei durchgehen.
Tymik
@tymik Der Zweck bestand darin, die Suche über den Cursor zu starten und jedes Ergebnis Schritt für Schritt durchzugehen. Aber ich habe Vim seit Jahren nicht mehr benutzt.
Basteln

Antworten:

50

1.  Es ist nicht schwer, das Verhalten durch eine zweistufige Substitution zu erreichen:

:,$s/BEFORE/AFTER/gc|1,''-&&

Zunächst wird für jede Zeile ein Ersetzungsbefehl ausgeführt, der von der aktuellen bis zum Ende der Datei beginnt. Dann wird dieser :substituteBefehl mit demselben Suchmuster, derselben Ersatzzeichenfolge und denselben Flags mit dem :& Befehl wiederholt (siehe :help :&).

Letzterer führt jedoch die Ersetzung für den Zeilenbereich von der ersten Zeile der Datei bis zur Zeile durch, in der die vorherige Kontextmarke gesetzt wurde, minus eins. Da der erste :substituteBefehl die Cursorposition speichert, bevor mit dem eigentlichen Ersetzen begonnen wird, ist die von adressierte  ''Zeile die Zeile, die die aktuelle war, bevor dieser Ersetzungsbefehl ausgeführt wurde. (Die '' Adresse bezieht sich auf die 'Pseudomarke; siehe :help :rangeund :help ''für Details.)

Beachten Sie, dass der zweite Befehl (nach dem | Befehlstrennzeichen - siehe :help :bar) keine Änderung erfordert, wenn das Muster oder die Flags im ersten geändert werden.

2.  Um einige Eingaben zu sparen und das Grundgerüst des obigen Substitutionsbefehls in der Befehlszeile aufzurufen, können Sie eine Zuordnung im Normalmodus wie folgt definieren:

:noremap <leader>R :,$s///gc\|1,''-&&<c-b><right><right><right><right>

Der nachfolgende <c-b><right><right><right><right>Teil ist erforderlich, um den Cursor zwischen die ersten beiden Schrägstriche zu setzen, damit der Benutzer mit der Eingabe des Suchmusters beginnen kann. Sobald das gewünschte Muster und Ersetzen fertig sind, kann der resultierende Befehl durch Drücken von ausgeführt werden Enter.

( Wenn Sie das Muster lieber eingeben möchten, sollten Sie //anstelle des ///obigen Mappings den Trennstrich selbst eingeben, gefolgt von der Ersatzzeichenfolge, anstatt den Cursor mit dem Rechtspfeil über den bereits vorhandenen Trennstrich zu bewegen das Ersatzteil.)

ib.
quelle
1
Das einzige Problem mit dieser Lösung ist, dass die Optionen last (l) und quit (q) nicht verhindern, dass der zweite Ersatzbefehl ausgeführt wird
Steve Vermeulen
@eventualEntropy Siehe meine Lösung unten zum Auffordern eines weiteren 'q'-Drucks.
Q335r49
2
Vergessen Sie nicht (wie ich), der Pipe zu entkommen, wenn Sie dies in eine Schlüsselzuordnung einfügen.
Jazzabeanie
11
Oh mein Gott, was bedeuten all diese willkürlichen Charaktere überhaupt? Wie hast du das gelernt?
Rocky Raccoon
2
@Tropilio: Dann kannst du so etwas ausprobieren : :noremap <leader>R :,$s///gc\|1,''-&&<c-b><right><right><right><right>. Es :,$s///gc|1,''-&&wird mit dem Cursor zwischen den ersten beiden Schrägstrichen in die Befehlszeile eingefügt. Sobald Sie das gewünschte Muster eingegeben und ersetzt haben, können Sie den resultierenden Befehl ausführen, indem Sie die Eingabetaste drücken .
ib.
174

Sie verwenden bereits einen Bereich, %der kurz für die 1,$Bedeutung der gesamten Datei steht. Um von der aktuellen Zeile zum Ende zu gelangen, verwenden Sie .,$. Der Punkt bedeutet aktuelle Zeile und $bedeutet die letzte Zeile. Der Befehl wäre also:

:.,$s/\vBEFORE/AFTER/gc

Es .kann jedoch davon ausgegangen werden, dass die oder die aktuelle Zeile entfernt werden kann:

:,$s/\vBEFORE/AFTER/gc

Weitere Hilfe finden Sie unter

:h range
Peter Rincker
quelle
Danke, das wusste ich schon. Ich möchte, dass das Suchen und Ersetzen abgeschlossen wird, sobald das Ende des Dokuments erreicht ist. Mit:., $ S macht es das nicht, es heißt nur "Muster nicht gefunden".
Basteln
7
Für "die nächsten zehn Zeilen":10:s/pattern/replace/gc
hier
10
Auch wenn es nicht das ist, wonach das OP gesucht hat, war dies für mich sehr hilfreich, danke!
Tobias J
51

%ist eine Verknüpfung für 1,$
(Vim help => :help :%gleich 1, $ (die gesamte Datei).)

. ist die Cursorposition, damit Sie dies tun können

:.,$s/\vBEFORE/AFTER/gc

Vom Anfang des Dokuments bis zum Cursor ersetzen

:1,.s/\vBEFORE/AFTER/gc

etc

Ich empfehle dringend, dass Sie das Handbuch über den Bereich lesen, :help rangeda so gut wie alle Befehle mit einem Bereich arbeiten.

mb14
quelle
Danke, das wusste ich schon. Ich möchte, dass das Suchen und Ersetzen abgeschlossen wird, sobald das Ende des Dokuments erreicht ist. Mit:., $ S macht es das nicht, es heißt nur "Muster nicht gefunden".
Basteln
@basteln: es gab Tippfehler im Beitrag // hast du kopiert und eingefügt?
sehe
Sie möchten also ALLES ersetzen, aber wenn Sie um Bestätigung bitten möchten, möchten Sie von der aktuellen Position aus starten. Dann mach es einfach zweimal.
mb14
Sie können stattdessen n und & verwenden.
mb14
hmm, ich denke n und & ist die beste Lösung, obwohl es nicht genau so ist, wie es sein sollte (mit der Ja / Nein-Eingabeaufforderung bei jedem Spiel). Aber auf jeden Fall gut genug, danke! :)
Basteln
4

Ich habe ENDLICH eine Lösung für die Tatsache gefunden, dass das Beenden der Suche bis zum Anfang der Datei reicht, ohne eine enorme Funktion zu schreiben ...

Sie würden nicht glauben, wie lange ich dafür gebraucht habe. Fügen Sie einfach eine Eingabeaufforderung zum Umbrechen hinzu: Wenn der Benutzer qerneut drückt , wird nicht umbrochen. Also im Grunde, beenden Suche durch Tippen qqstatt q! (Und wenn Sie einpacken möchten, geben Sie einfach ein y.)

:,$s/BEFORE/AFTER/gce|echo 'Continue at beginning of file? (y/q)'|if getchar()!=113|1,''-&&|en

Ich habe dies tatsächlich einem Hotkey zugeordnet. Wenn Sie beispielsweise jedes Wort unter dem Cursor ab der aktuellen Position suchen und ersetzen möchten, durch q*:

exe 'nno q* :,$s/\<<c-r>=expand("<cword>")<cr>\>//gce\|echo "Continue at beginning of file? (y/q)"\|if getchar()==121\|1,''''-&&\|en'.repeat('<left>',77)
q335r49
quelle
Meiner Meinung nach führt dieser Ansatz - das Einfügen einer zusätzlichen Eingabeaufforderung zwischen den beiden in meiner Antwort vorgeschlagenen Befehlen - zu unnötiger Komplexität, ohne die Benutzerfreundlichkeit zu verbessern. In Originalversion , wenn Sie den ersten Substitutions Befehl beenden (mit q, loder Esc ) und wollen nicht von oben fortzusetzen, können Sie bereits drücken qoder Esc erneut den zweiten Befehl sofort zu beenden. Das heißt, das vorgeschlagene Hinzufügen der Eingabeaufforderung scheint die bereits vorhandene Funktionalität effektiv zu duplizieren.
ib.
Alter ... hast du deinen Ansatz ausprobiert? Angenommen, ich möchte die nächsten drei Vorkommen von "let" durch "unlet" ersetzen und dann - dies ist der Schlüssel - den Absatz weiter bearbeiten . Ihr Ansatz "Drücken Sie y, y, y, drücken Sie jetzt q. Jetzt beginnen Sie mit der Suche in der ersten Zeile des Absatzes. Drücken Sie erneut q, um den Vorgang zu beenden. Gehen Sie nun zu Ihrer vorherigen Position zurück, um die Bearbeitung fortzusetzen. Ok, g;. Also im Grunde: yyyqq ... verwirrt werden ... g; (oder u ^ R) Meine Methode: yyyqq
q335r49
Die ideale Methode ist natürlich yyyq, die Bearbeitung ohne verwirrende Sprünge fortzusetzen. Aber es yyyqqist fast genauso gut, wenn man ein doppeltes Tippen in Bezug auf die Benutzerfreundlichkeit als ein einfaches Tippen betrachtet.
Q335r49
Weder der ursprüngliche Befehl noch seine Version mit einer Eingabeaufforderung bringen den Cursor in diesem Szenario an seine vorherige Position zurück. Ersteres verlässt den Benutzer beim ersten Auftreten des Musters von oben (wo es zum Zeitpunkt des qzweiten Drückens war). Letzterer verlässt den Cursor beim letzten Auftreten, das ersetzt wurde (kurz bevor das erste qgedrückt wurde).
ib.
1
Wenn es in der Originalversion vorzuziehen ist, den Cursor automatisch an seine vorherige Position zurückzusetzen (ohne dass der Benutzer Strg + O eingibt ), kann der norm!``Befehl einfach angehängt werden : :,$s/BEFORE/AFTER/gc|1,''-&&|norm!``.
ib.
3

Hier ist etwas sehr Grobes, das die Bedenken bezüglich des Umlaufens der Suche mit dem zweistufigen Ansatz ( :,$s/BEFORE/AFTER/gc|1,''-&&) oder mit einem Zwischenprodukt "Weiter am Anfang der Datei?" Ansatz:

" Define a mapping that calls a command.
nnoremap <Leader>e :Substitute/\v<<C-R>=expand('<cword>')<CR>>//<Left>

" And that command calls a script-local function.
command! -nargs=1 Substitute call s:Substitute(<q-args>)

function! s:Substitute(patterns)
  if getregtype('s') != ''
    let l:register=getreg('s')
  endif
  normal! qs
  redir => l:replacements
  try
    execute ',$s' . a:patterns . 'gce#'
  catch /^Vim:Interrupt$/
    return
  finally
    normal! q
    let l:transcript=getreg('s')
    if exists('l:register')
      call setreg('s', l:register)
    endif
  endtry
  redir END
  if len(l:replacements) > 0
    " At least one instance of pattern was found.
    let l:last=strpart(l:transcript, len(l:transcript) - 1)
    " Note: type the literal <Esc> (^[) here with <C-v><Esc>:
    if l:last ==# 'l' || l:last ==# 'q' || l:last ==# '^['
      " User bailed.
      return
    endif
  endif

  " Loop around to top of file and continue.
  " Avoid unwanted "Backwards range given, OK to swap (y/n)?" messages.
  if line("''") > 1
    1,''-&&"
  endif
endfunction

Diese Funktion verwendet einige Hacks, um zu überprüfen, ob wir uns nach oben wickeln sollen:

  • Kein Umbruch, wenn der Benutzer "l", "q" oder "Esc" gedrückt hat, was auf einen Abbruchwunsch hinweist.
  • Erkennen Sie diesen letzten Tastendruck, indem Sie ein Makro in das Register "s" aufnehmen und das letzte Zeichen davon überprüfen.
  • Vermeiden Sie das Überschreiben eines vorhandenen Makros, indem Sie das Register "s" speichern / wiederherstellen.
  • Wenn Sie mit dem Befehl bereits ein Makro aufzeichnen, sind alle Wetten deaktiviert.
  • Versucht, mit Interrupts das Richtige zu tun.
  • Vermeidet "Rückwärtsbereich" -Warnungen mit einem expliziten Schutz.
zusammenzuckend
quelle
1
Ich habe dies in ein winziges Plug-In extrahiert und hier auf GitHub gestellt .
wincent
Einfach dein Plugin installiert, es funktioniert wie Charme !! Vielen Dank dafür!
Tropilio
1

Ich bin zu spät zur Party, aber ich habe mich zu oft auf solche späten Stackoverflow-Antworten verlassen, um es nicht zu tun. Ich habe Hinweise zu reddit und stackoverflow gesammelt. Die beste Option ist, das \%>...cMuster in der Suche zu verwenden, das erst nach Ihrem Cursor übereinstimmt.

Das heißt, es bringt auch das Muster für den nächsten Austauschschritt durcheinander und ist schwer zu tippen. Um diesen Effekten entgegenzuwirken, muss eine benutzerdefinierte Funktion das Suchmuster anschließend filtern und somit zurücksetzen. Siehe unten.

Ich habe mich mit einem Mapping auseinandergesetzt, das das nächste Vorkommen ersetzt und zu dem folgenden springt, und nicht mehr (war sowieso mein Ziel). Ich bin sicher, darauf aufbauend kann eine globale Substitution ausgearbeitet werden. Denken Sie daran, wenn Sie an einer Lösung arbeiten, die darauf abzielt, :%s/.../.../gdass das folgende Muster Übereinstimmungen in allen Zeilen herausfiltert , die noch an der Cursorposition verbleiben. Sie werden jedoch nach Abschluss der einzelnen Ersetzung bereinigt, sodass dieser Effekt direkt nach dem Springen verloren geht nächstes Spiel und kann somit alle Spiele einzeln durchlaufen.

fun! g:CleanColFromPattern(prevPattern) return substitute(a:prevPattern, '\V\^\\%>\[0-9]\+c', '', '') endf nmap <F3>n m`:s/\%><C-r>=col(".")-1<CR>c<C-.r>=g:CleanColFromPattern(getreg("/"))<CR>/~/&<CR>:call setreg("/", g:CleanColFromPattern(getreg("/")))<CR>``n

simlei
quelle
Danke, ich stimme zu, dass eine späte Antwort oft hilfreich ist. Ich persönlich benutze Vim seit langer Zeit nicht mehr (aus solchen Gründen), aber ich bin sicher, dass viele andere Leute dies hilfreich finden werden.
Basteln