Recherche globale et remplacement dans Vim, en commençant par la position du curseur et en retournant

112

Lorsque je recherche avec la / commande en mode normal:

/\vSEARCHTERM

Vim commence la recherche à partir de la position du curseur et continue vers le bas, s'enroulant vers le haut. Cependant, lorsque je recherche et remplace à l'aide de la :substitutecommande:

:%s/\vBEFORE/AFTER/gc

Vim commence à la place en haut du fichier.

Existe-t-il un moyen pour que Vim effectue une recherche et un remplacement en commençant par la position du curseur et en retournant vers le haut une fois qu'il atteint la fin?

Basteln
la source
2
\vpattern- motif `` très magique '': les caractères non alphanumériques sont interprétés comme des symboles spéciaux de regex (aucun échappement nécessaire)
Carlos Araya
quel est le point de partir de la position actuelle et d'enrouler autour du fichier au lieu d'utiliser simplement %? Je ne vois aucune utilité raisonnable pour que vous parcouriez tout le dossier de toute façon.
tymik le
@tymik Le but était de lancer la recherche à partir du curseur, et de parcourir chaque résultat étape par étape. Mais je n'ai pas utilisé Vim depuis des années maintenant.
basteln

Réponses:

50

1.  Il n'est pas difficile d'obtenir le comportement en utilisant une substitution en deux étapes:

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

Tout d'abord, la commande de substitution est exécutée pour chaque ligne à partir de la ligne actuelle jusqu'à la fin du fichier:

,$s/BEFORE/AFTER/gc

Ensuite, cette :substitutecommande est répétée avec le même modèle de recherche, la même chaîne de remplacement et les mêmes indicateurs, à l'aide de la :& commande (voir :help :&):

1,''-&&

Ce dernier, cependant, effectue la substitution sur la plage de lignes de la première ligne du fichier à la ligne où la marque de contexte précédente a été définie, moins un. Étant donné que la première :substitutecommande stocke la position du curseur avant de commencer les remplacements réels, la ligne adressée par  ''est la ligne qui était la ligne actuelle avant l'exécution de cette commande de substitution. (L' '' adresse fait référence à la ' pseudo-marque; voir :help :rangeet :help ''pour plus de détails.)

Notez que la deuxième commande (après le | séparateur de commande - voir :help :bar) ne nécessite aucune modification lorsque le modèle ou les indicateurs sont modifiés dans le premier.

2.  Pour enregistrer un peu de frappe, afin d'afficher le squelette de la commande de substitution ci-dessus dans la ligne de commande, on peut définir un mappage en mode normal, comme ceci:

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

La <c-b><right><right><right><right>partie de fin est nécessaire pour déplacer le curseur au début de la ligne de commande ( <c-b>) puis quatre caractères vers la droite ( <right> × 4), le plaçant ainsi entre les deux premiers signes de barre oblique, prêt pour que l'utilisateur commence à taper le motif de recherche . Une fois que le motif souhaité et le remplacement sont prêts, la commande résultante peut être exécutée en appuyant sur Enter.

(On pourrait envisager d'avoir //au lieu de ///dans le mappage ci-dessus, si l'on préfère taper le motif, puis taper soi-même la barre oblique de séparation, suivie de la chaîne de remplacement, au lieu d'utiliser la flèche droite pour déplacer le curseur sur une barre oblique de séparation déjà présente en commençant la pièce de rechange.)

ib.
la source
1
Le seul problème avec cette solution est que les options last (l) et quit (q) n'empêchent pas la deuxième commande de substitution de s'exécuter
Steve Vermeulen
@eventualEntropy Voir ma solution ci-dessous pour demander une autre presse «q».
q335r49
2
N'oubliez pas (comme je l'ai fait) d'échapper au tuyau si vous mettez cela dans un mappage de touches.
jazzabeanie
11
Oh mon dieu, que signifient tous ces personnages arbitraires? Comment as-tu appris ça?
rocky ratoon
2
@Tropilio: Ensuite , vous pouvez essayer quelque chose comme ceci: :noremap <leader>R :,$s///gc\|1,''-&&<c-b><right><right><right><right>. Il met :,$s///gc|1,''-&&dans la ligne de commande avec le curseur entre les deux premiers signes de barre oblique. Une fois que vous avez tapé le modèle et le remplacement souhaités, vous pouvez exécuter la commande résultante en appuyant sur Entrée .
ib.
174

Vous utilisez déjà une plage,, %qui est un raccourci pour 1,$désigner l'ensemble du fichier. Pour aller de la ligne actuelle à la fin que vous utilisez .,$. La période signifie la ligne courante et $la dernière ligne. La commande serait donc:

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

Mais la .ligne ou courante peut être supposée donc peut être supprimée:

:,$s/\vBEFORE/AFTER/gc

Pour plus d'aide, consultez

:h range
Peter Rincker
la source
Merci, je savais déjà à ce sujet. Je veux que la recherche et le remplacement se terminent une fois qu'elle atteint la fin du document. Avec:., $ S ça ne fait pas ça, ça dit juste "Pattern not found".
basteln le
7
Pour "les dix prochaines lignes":10:s/pattern/replace/gc
ici le
10
même si ce n'est pas ce que l'OP recherchait, cela m'a été très utile, merci!
Tobias J
51

%est un raccourci pour 1,$
(Aide Vim => :help :%égal à 1, $ (le fichier entier).)

. est la position du curseur que vous pouvez faire

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

A remplacer depuis le début du document jusqu'au curseur

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

etc

Je vous suggère fortement de lire le manuel sur la plage :help rangecar à peu près toutes les commandes fonctionnent avec une plage.

mb14
la source
Merci, je savais déjà à ce sujet. Je veux que la recherche et le remplacement se terminent une fois qu'elle atteint la fin du document. Avec:., $ S ça ne fait pas ça, ça dit juste "Pattern not found".
basteln le
@basteln: il y avait une faute de frappe dans le post // avez-vous copié et collé?
sehe le
Donc vous voulez tout remplacer mais comme vous voulez demander confirmation vous voulez partir de la position actuelle. Faites-le deux fois alors.
mb14 le
vous pouvez utiliser n et & à la place.
mb14 le
hmm, je suppose que n et & est la meilleure solution, même si ce n'est pas exactement ce que cela devrait être (avec l'invite oui / non à chaque correspondance). Mais certainement assez bien, merci! :)
basteln
4

J'ai enfin trouvé une solution au fait que quitter la recherche revient au début du fichier sans écrire une fonction énorme ...

Vous ne croiriez pas combien de temps il m'a fallu pour trouver ça. Ajoutez simplement une invite pour savoir s'il faut envelopper: si l'utilisateur appuie à qnouveau, ne pas envelopper. Donc, en gros, quittez la recherche en appuyant sur qqau lieu de q! (Et si vous voulez envelopper, tapez simplement y.)

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

En fait, j'ai mappé cela sur une touche de raccourci. Ainsi, par exemple, si vous souhaitez rechercher et remplacer chaque mot sous le curseur, à partir de la position actuelle, par 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
la source
À mon avis, cette approche - insérer une invite supplémentaire entre les deux commandes proposées dans ma réponse - ajoute une complexité inutile sans améliorer la convivialité. Dans la version originale , si vous quittez la première commande de substitution (avec q, lou Esc ) et ne veulent pas continuer du haut, vous pouvez déjà appuyer qou Echap nouveau pour quitter la deuxième commande tout de suite. Autrement dit, l'ajout proposé de l'invite semble dupliquer efficacement la fonctionnalité déjà présente.
ib.
Mec ... avez-vous essayé votre approche? Disons que je veux remplacer les 3 prochaines occurrences de "let" par "unlet", puis - c'est la clé - continuer à éditer le paragraphe . Votre approche "Appuyez sur y, y, y, maintenant appuyez sur q. Maintenant, vous commencez la recherche à la première ligne du paragraphe. Appuyez à nouveau sur q pour quitter. Maintenant, revenez à l'endroit où vous étiez auparavant, pour continuer l'édition. Ok, g ;. Donc, en gros: yyyqq ... devenir confus ... g; (ou u ^ R) Ma méthode: yyyqq
q335r49
La méthode idéale est, bien sûr yyyq, de continuer à éditer, sans sauts désorientants. Mais yyyqqc'est presque aussi bon, si vous considérez un double tap à peu près un seul robinet en termes de facilité d'utilisation.
q335r49
Ni la commande d'origine ni sa version avec une invite ne ramènent le curseur à sa position précédente dans ce scénario. Le premier laisse l'utilisateur à la première occurrence du motif depuis le haut (là où il se trouvait au moment de qla deuxième pression ). Ce dernier laisse le curseur à la dernière occurrence remplacée (juste avant que la première ne qsoit pressée).
ib.
1
Dans la version originale , s'il est préférable de revenir automatiquement le curseur sur sa position avant (sans la saisie de l' utilisateur Ctrl + O ), on peut juste ajouter le norm!``commande: :,$s/BEFORE/AFTER/gc|1,''-&&|norm!``.
ib.
3

Voici quelque chose de très grossier qui répond aux préoccupations concernant l'enroulement de la recherche avec l'approche en deux étapes ( :,$s/BEFORE/AFTER/gc|1,''-&&) ou avec un intermédiaire "Continuer au début du fichier?" approche:

" 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

Cette fonction utilise quelques hacks pour vérifier si nous devons aller vers le haut:

  • Pas de wrapping si l'utilisateur a appuyé sur "l", "q" ou "Esc", l'un d'entre eux indiquant un désir d'abandonner.
  • Détectez cette dernière pression sur une touche en enregistrant une macro dans le registre "s" et en inspectant le dernier caractère de celui-ci.
  • Évitez d'écraser une macro existante en sauvegardant / en restaurant le registre "s".
  • Si vous enregistrez déjà une macro lors de l'utilisation de la commande, tous les paris sont ouverts.
  • Tente de faire la bonne chose avec les interruptions.
  • Évite les avertissements de «portée arrière» avec une protection explicite.
wincent
la source
1
J'ai extrait ceci dans un petit plug-in et l'ai mis sur GitHub ici .
wincent
Je viens d'installer votre plugin, ça marche comme du charme !! Merci beaucoup d'avoir fait ça!
Tropilio
1

Je suis en retard à la fête, mais je me suis trop souvent appuyé sur des réponses de stackoverflow aussi tardives pour ne pas le faire. J'ai rassemblé des conseils sur reddit et stackoverflow, et la meilleure option est d'utiliser le \%>...cmodèle dans la recherche, qui ne correspond qu'après votre curseur.

Cela dit, cela gâche également le modèle de la prochaine étape de remplacement et est difficile à taper. Pour contrer ces effets, une fonction personnalisée doit ensuite filtrer le modèle de recherche, le réinitialisant ainsi. Voir ci-dessous.

Je me suis disputé une cartographie qui remplace l'occurrence suivante et passe à la suivante après, et pas plus (c'était mon objectif de toute façon). Je suis sûr que, sur cette base, une substitution mondiale peut être élaborée. Gardez à l'esprit lorsque vous travaillez sur une solution visant quelque chose comme :%s/.../.../gça, le modèle ci-dessous filtre les correspondances dans toutes les lignes à gauche de la position du curseur - mais est nettoyé après la fin de la substitution unique, donc il perd cet effet directement après, saute à la match suivant et peut ainsi parcourir tous les matchs un par un.

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
la source
Merci, je conviens qu'une réponse tardive est souvent utile. Personnellement, je n'utilise plus Vim depuis longtemps (pour des raisons comme celle-ci), mais je suis sûr que beaucoup d'autres personnes trouveront cela utile.
basteln