Ricerca e sostituzione globale in Vim, partendo dalla posizione del cursore e avvolgendo


112

Quando cerco con il / comando modalità normale:

/\vSEARCHTERM

Vim inizia la ricerca dalla posizione del cursore e continua verso il basso, spostandosi verso l'alto. Tuttavia, quando cerco e sostituisco utilizzando il :substitutecomando:

:%s/\vBEFORE/AFTER/gc

Vim inizia invece all'inizio del file.

C'è un modo per fare in modo che Vim esegua la ricerca e la sostituzione partendo dalla posizione del cursore e andando verso l'alto una volta raggiunta la fine?


2
\vpattern- Pattern "molto magico": i caratteri non alfanumerici sono interpretati come simboli speciali di espressioni regolari (non è necessario l'escape)
Carlos Araya

qual è il punto per iniziare dalla posizione corrente e avvolgere il file invece di usare solo %? non vedo un uso ragionevole per questo stai comunque esaminando l'intero file.
tymik

@tymik Lo scopo era iniziare la ricerca dal cursore e passare attraverso ogni risultato passo dopo passo. Ma non uso Vim da anni ormai.
basteln

Risposte:


50

1.  Non è difficile ottenere il comportamento utilizzando una sostituzione in due fasi:

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

Per prima cosa viene eseguito il comando di sostituzione per ogni riga a partire da quella corrente fino alla fine del file:

,$s/BEFORE/AFTER/gc

Quindi, quel :substitutecomando viene ripetuto con lo stesso modello di ricerca, stringa di sostituzione e flag, utilizzando il :& comando (vedi :help :&):

1,''-&&

Quest'ultimo, tuttavia, esegue la sostituzione sull'intervallo di righe dalla prima riga del file alla riga in cui è stato impostato il contrassegno di contesto precedente, meno uno. Poiché il primo :substitutecomando memorizza la posizione del cursore prima di iniziare le sostituzioni effettive, la riga indirizzata da  ''è la riga che era quella corrente prima che il comando di sostituzione fosse eseguito. (L' '' indirizzo si riferisce allo ' pseudo-marchio; vedere :help :rangee :help ''per i dettagli.)

Notare che il secondo comando (dopo il | separatore di comandi, vedere :help :bar) non richiede alcuna modifica quando il modello o le bandiere vengono alterati nel primo.

2.  Per salvare un po 'di digitazione, al fine di visualizzare lo scheletro del comando di sostituzione sopra nella riga di comando, si può definire una mappatura in modalità Normale, in questo modo:

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

La <c-b><right><right><right><right>parte finale è necessaria per spostare il cursore all'inizio della riga di comando ( <c-b>) e poi quattro caratteri a destra ( <right> × 4), mettendolo così tra i primi due segni di barra, pronto per l'utente per iniziare a digitare il modello di ricerca . Una volta che il modello desiderato e la sostituzione sono pronti, il comando risultante può essere eseguito premendo Enter.

(Si potrebbe considerare di avere //invece che ///nella mappatura sopra, se si preferisce digitare il modello, quindi digitare personalmente la barra di separazione, seguita dalla stringa di sostituzione, invece di usare la freccia destra per spostare il cursore su una barra di separazione già presente che inizia la parte di ricambio.)


1
L'unico problema con questa soluzione è che le opzioni durano (l) ed esci (q) non interrompono l'esecuzione del secondo comando sostitutivo
Steve Vermeulen

@eventualEntropy Vedere la mia soluzione di seguito sulla richiesta di un'altra stampa "q".
q335r49

2
Non dimenticare (come ho fatto io) di uscire dal pipe se lo metti in una mappatura dei tasti.
jazzabeanie,

11
Oh mio Dio, cosa significano tutti quei caratteri arbitrari. Come l'hai imparato?
Procione roccioso il

2
@Tropilio: Poi si può provare qualcosa di simile: :noremap <leader>R :,$s///gc\|1,''-&&<c-b><right><right><right><right>. Si inserisce :,$s///gc|1,''-&&nella riga di comando con il cursore tra i primi due segni di barra. Dopo aver digitato il modello e la sostituzione desiderati, è possibile eseguire il comando risultante premendo Invio .
ib.

174

Stai già utilizzando un intervallo, %che è l'abbreviazione per 1,$indicare l'intero file. Per passare dalla riga corrente alla fine che usi .,$. Il punto indica la riga corrente e $l'ultima riga. Quindi il comando sarebbe:

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

Ma la .linea o corrente può essere presunta quindi può essere rimossa:

:,$s/\vBEFORE/AFTER/gc

Per ulteriori informazioni vedere

:h range

Grazie, lo sapevo già. Voglio che la ricerca e sostituzione vada a capo una volta raggiunta la fine del documento. Con:., $ S non lo fa, dice solo "Pattern non trovato".
basteln

7
Per "le prossime dieci righe":10:s/pattern/replace/gc
qui

10
anche se non è quello che stava cercando l'OP, questo mi è stato molto utile, grazie!
Tobias J

51

%è una scorciatoia per 1,$
(Vim help => :help :%uguale a 1, $ (l'intero file).)

. è la posizione del cursore così puoi farlo

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

Da sostituire dall'inizio del documento fino al cursore

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

eccetera

Suggerisco caldamente di leggere il manuale sull'intervallo :help rangepoiché praticamente tutti i comandi funzionano con un intervallo.


Grazie, lo sapevo già. Voglio che la ricerca e sostituzione vada a capo una volta raggiunta la fine del documento. Con:., $ S non lo fa, dice solo "Pattern non trovato".
basteln

@basteln: c'era un errore di battitura nel post // hai copiato e incollato?
guarda il

Quindi vuoi sostituire TUTTO ma siccome vuoi chiedere conferma vuoi partire dalla posizione attuale. Allora fallo due volte.
mb14

puoi usare n e & invece.
mb14

hmm, immagino che n e & sia la soluzione migliore, anche se non è esattamente come dovrebbe essere (con il prompt sì / no su ogni corrispondenza). Ma decisamente abbastanza buono, grazie! :)
basteln

4

FINALMENTE ho trovato una soluzione al fatto che abbandonando la ricerca si va a capo all'inizio del file senza scrivere una funzione enorme ...

Non crederesti quanto tempo mi ci è voluto per tirarlo fuori. Aggiungi semplicemente un messaggio di richiesta se eseguire il wrapping: se l'utente preme di qnuovo, non eseguire il wrapping. Quindi, in pratica, esci dalla ricerca toccando qqinvece di q! (E se vuoi andare a capo, basta digitare y.)

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

In realtà l'ho mappato su un tasto di scelta rapida. Quindi, ad esempio, se vuoi cercare e sostituire ogni parola sotto il cursore, partendo dalla posizione corrente, con 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)

A mio parere, questo approccio - inserendo un prompt aggiuntivo tra i due comandi proposti nella mia risposta - aggiunge complessità inutili senza migliorare l'usabilità. Nella versione originale , se esci dal primo comando di sostituzione (con q, lo Esc ) e non vuoi continuare dall'alto, puoi già premere di nuovo qo Esc per uscire subito dal secondo comando. Cioè, l'aggiunta proposta del prompt sembra duplicare efficacemente la funzionalità già presente.
ib.

Amico ... hai provato il tuo approccio? Diciamo che voglio sostituire le prossime 3 occorrenze di "let" con "unlet", e quindi - questa è la chiave - continuare a modificare il paragrafo . Il tuo approccio "Premi y, y, y, ora premi q. Ora inizi a cercare dalla prima riga del paragrafo. Premi di nuovo q per uscire. Ora torna dove eri prima, per continuare a modificare. Ok, g ;. Quindi, in pratica: yyyqq ... diventa confuso ... g; (o u ^ R) Il mio metodo: yyyqq
q335r49

Il metodo ideale è, ovviamente yyyq, continuare a modificare, senza salti disorientanti. Ma yyyqqè quasi altrettanto buono, se consideri un doppio tocco praticamente un singolo tocco in termini di facilità d'uso.
q335r49

Né il comando originale né la sua versione con un prompt riportano il cursore alla sua posizione precedente in quello scenario. Il primo lascia l'utente alla prima occorrenza del motivo dall'alto (dove si trovava al momento della qseconda pressione ). Quest'ultimo lascia il cursore sull'ultima occorrenza che è stata sostituita (appena prima che la prima qfosse premuta).
ib.

1
Nella versione originale , se è preferibile restituire automaticamente il cursore nella posizione precedente (senza che l'utente digitando Ctrl + O ), si può solo appendere l' norm!``comando: :,$s/BEFORE/AFTER/gc|1,''-&&|norm!``.
ib.

3

Ecco qualcosa di molto approssimativo che risolve le preoccupazioni relative al completamento della ricerca con l'approccio in due fasi ( :,$s/BEFORE/AFTER/gc|1,''-&&) o con un "Continuare all'inizio del file?" approccio:

" 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

Questa funzione utilizza un paio di hack per verificare se è necessario andare a capo all'inizio:

  • Nessun ritorno a capo se l'utente ha premuto "l", "q" o "Esc", ognuno dei quali indica il desiderio di interrompere.
  • Rileva l'ultima pressione di un tasto registrando una macro nel registro "s" e controllandone l'ultimo carattere.
  • Evita di sovrascrivere una macro esistente salvando / ripristinando il registro "s".
  • Se stai già registrando una macro quando usi il comando, tutte le scommesse sono disattivate.
  • Cerca di fare la cosa giusta con le interruzioni.
  • Evita gli avvisi di "portata all'indietro" con una guardia esplicita.

1
L'ho estratto in un minuscolo plug-in e l' ho inserito su GitHub qui .
sussulta il

Appena installato il tuo plugin, funziona a meraviglia !! Grazie mille per averlo fatto!
Tropilio

1

Sono in ritardo alla festa, ma troppo spesso mi sono affidato a risposte così ritardate dello stackoverflow per non farlo. Ho raccolto suggerimenti su reddit e stackoverflow e l'opzione migliore è utilizzare il \%>...cpattern nella ricerca, che corrisponde solo dopo il cursore.

Detto questo, rovina anche il modello per il passaggio di sostituzione successivo ed è difficile da digitare. Per contrastare questi effetti, una funzione personalizzata deve successivamente filtrare il modello di ricerca, ripristinandolo. Vedi sotto.

Mi sono conteso con una mappatura che sostituisce la ricorrenza successiva e salta a quella successiva, e non di più (era comunque il mio obiettivo). Sono sicuro che, basandosi su questo, si possa elaborare una sostituzione globale. Tenere presente che quando si lavora su una soluzione che mira a qualcosa del genere, :%s/.../.../gil motivo sotto filtra le corrispondenze in tutte le righe a sinistra della posizione del cursore, ma viene ripulito dopo il completamento della singola sostituzione, quindi perde quell'effetto subito dopo, salta al partita successiva e quindi è in grado di scorrere tutte le partite una per una.

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


Grazie, sono d'accordo sul fatto che una risposta in ritardo è spesso utile. Personalmente non uso più Vim da molto tempo (per ragioni come questa), ma sono sicuro che molte altre persone lo troveranno utile.
basteln
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.