Fusionner plusieurs lignes (deux blocs) dans Vim


323

Je voudrais fusionner deux blocs de lignes dans Vim, c'est-à-dire prendre des lignes n..met les ajouter à des lignes a..b. Si vous préférez une explication de pseudocode:[a[i] + b[i] for i in min(len(a), len(b))]

Exemple:

abc
def
...

123
45
...

devrait devenir

abc123
def45

Existe-t-il un bon moyen de le faire sans faire de copier-coller manuellement?


Alors ... vous voulez rejoindre des lignes alternées? Autrement dit, vous voulez rejoindre la ligne xavec join x+2?
larsks

1
Non, j'ai deux blocs distincts. Pseudocode-ish:[a[i] + b[i] for i in min(len(a), len(b))]
ThiefMaster

2
Voir une question similaire (et une réponse!) Ici
NWS

Réponses:


880

Vous pouvez certainement faire tout cela avec un seul copier / coller (en utilisant la sélection en mode bloc), mais je suppose que ce n'est pas ce que vous voulez.

Si vous voulez le faire avec seulement des commandes Ex

:5,8del | let l=split(@") | 1,4s/$/\=remove(l,0)/

va transformer

work it 
make it 
do it 
makes us 
harder
better
faster
stronger
~

dans

work it harder
make it better
do it faster
makes us stronger
~

MISE À JOUR: Une réponse avec autant de votes positifs mérite une explication plus approfondie.

Dans Vim, vous pouvez utiliser le caractère pipe ( |) pour chaîner plusieurs commandes Ex, donc ce qui précède est équivalent à

:5,8del
:let l=split(@")
:1,4s/$/\=remove(l,0)/

De nombreuses commandes Ex acceptent une plage de lignes comme argument de préfixe - dans le cas ci-dessus, 5,8avant delet 1,4avant les/// spécifient sur quelles lignes les commandes opèrent.

delsupprime les lignes données. Il peut prendre un argument de registre, mais quand il n'est pas donné, il vide les lignes dans le registre sans nom @", tout comme la suppression en mode normal. let l=split(@")divise ensuite les lignes supprimées en une liste, en utilisant le délimiteur par défaut: les espaces. Pour fonctionner correctement sur une entrée qui avait un espace dans les lignes supprimées, comme:

more than 
hour 
our 
never 
ever
after
work is
over
~

nous avions besoin de spécifier un séparateur différent, pour éviter « le travail est » d'être divisé en deux éléments de la liste: let l=split(@","\n").

Enfin, dans la substitution s/$/\=remove(l,0)/, nous remplaçons la fin de chaque ligne ( $) par la valeur de l'expression remove(l,0). remove(l,0)modifie la liste l, en supprimant et en renvoyant son premier élément. Cela nous permet de remplacer les lignes supprimées dans l'ordre dans lequel nous les lisons. Nous pourrions remplacer les lignes supprimées dans l'ordre inverse en utilisant remove(l,-1).


1
hmm ... Je n'ai qu'à appuyer une fois sur Entrée. Et il n'insérera pas d'espace entre les deux moitiés. S'il y a un espace de fin sur les lignes (comme dans l'exemple "work it"), il sera toujours là. Vous pouvez vous débarrasser de tout espace de fin en utilisant s/\s*$/au lieu de s/$/.
rampion

1
Merci pour l'explication. :sil5,8del | let l=split(@") | sil1,4s/$/\=remove(l,0)/ | call histdel("/", -1) | nohlssemble être encore mieux car il nettoie l'historique de recherche après l'exécution. Et il n'affiche pas le message "x plus / moins de lignes" m'obligeant à appuyer sur Entrée.
ThiefMaster

11
Si vous souhaitez une référence complète vim pour cette réponse: :help range, :help :d, :help :let, :help split(), :help :s, :help :s\=, :help remove().
Benoit

16
Faire en sorte que des gens comme vous veuillent publier des réponses comme celle-ci est la raison pour laquelle je suis devenu modérateur. Bon spectacle :)
Tim Post

1
Il y a un problème s'il n'y a pas d'espace blanc après les 4 premières phrases.
Reman

58

Une commande élégante et concise Ex résoudre le problème peut être obtenue en combinant les :global, :moveet les :joincommandes. En supposant que le premier bloc de lignes commence sur la première ligne du tampon et que le curseur se trouve sur la ligne précédant immédiatement la première ligne du deuxième bloc, la commande est la suivante.

:1,g/^/''+m.|-j!

Pour une explication détaillée de cette technique, voir ma réponse à une question similaire Comportement de « Vim paste -d» hors de la boîte? ».


E16: Invalid Range- mais ça marche quand même. Lors de la suppression, 1,cela fonctionne sans l'erreur.
ThiefMaster

Et tant pis, vous ne pouvez pas accepter plus d'une réponse - sinon la vôtre aurait aussi une marque verte!
ThiefMaster

3
Très agréable! Je ne connaissais pas :moveet :join!, ni ce que ''signifiait comme argument de plage ( :help '') et ce +que -signifiait comme modificateurs de plage ( :help range). Merci!
rampion

@ThiefMaster: la commande est conçue pour être utilisée lorsque les deux plages de lignes à joindre sont adjacentes, de sorte que le curseur se trouve initialement sur la dernière ligne du premier bloc qui précède immédiatement la première ligne du deuxième bloc. (Voir la réponse et la question liées.) La commande fonctionne comme prévu même si le nombre de lignes dans les blocs diffère, bien qu'elle donne un message d'erreur dans ce cas. On peut supprimer les messages d'erreur en ajoutant sil!à la commande.
ib.

2
@ib .: Je pense que ce serait une bonne idée de mettre l'explication détaillée dans cette réponse aussi.
ThiefMaster

44

Pour joindre des blocs de ligne, vous devez suivre les étapes suivantes:

  1. Allez à la troisième ligne: jj
  2. Entrer en mode de blocage visuel: CTRL-v
  3. Ancrez le curseur à la fin de la ligne (important pour les lignes de longueur différente): $
  4. Allez à la fin: CTRL-END
  5. Coupez le bloc: x
  6. Allez à la fin de la première ligne: kk$
  7. Collez le bloc ici: p

Le mouvement n'est pas le meilleur (je ne suis pas un expert), mais il fonctionne comme vous le vouliez. J'espère qu'il y en aura une version plus courte.

Voici les prérequis pour que cette technique fonctionne bien:

  • Toutes les lignes du bloc de départ (dans l'exemple de la question abcet def) ont la même longueur XOR
  • la première ligne du bloc de départ est la plus longue et vous ne vous souciez pas des espaces supplémentaires entre les deux) XOR
  • La première ligne du bloc de départ n'est pas la plus longue, et vous avez des espaces supplémentaires à la fin.

Woah, c'est intéressant! Je n'aurais jamais pensé que cela fonctionnerait de cette façon.
voithos

13
Cela fonctionne comme voulu uniquement parce que abcet defsont de la même longueur. La sélection de bloc conservera les retraits du texte supprimé, donc si le curseur est sur une ligne courte lors de la mise en place du texte, les lignes sont insérées entre les lettres sur les plus longues - et des espaces ajoutés aux plus courtes si le curseur était sur une plus longue une.
Izkata

En effet ... Existe-t-il un moyen de le faire correctement en utilisant le copier-coller de blocs? C'est le moyen le plus simple de le faire après tout (surtout si vous ne pouvez pas rechercher les moyens les plus compliqués ici pour une raison quelconque).
ThiefMaster

2
Comme @Izkata le dit, vous rencontrerez le problème d'insertion de texte entre les lignes plus longues. Pour contourner cela, ajoutez simplement plus d'espaces à la fin de la première ligne pour la rendre la plus longue, puis collez votre bloc de texte. Une fois cela fait, compresser plusieurs espaces en un est aussi simple que:%s/ \+/ /g
Khaja Minhajuddin


19

Voici comment je le ferais (avec le curseur sur la première ligne):

qama:5<CR>y$'a$p:5<CR>dd'ajq3@a

Vous devez savoir deux choses:

  • Le numéro de ligne sur lequel la première ligne du deuxième groupe commence (5 dans mon cas), et
  • le nombre de lignes dans chaque groupe (3 dans mon exemple).

Voici ce qui se passe:

  • qaenregistre tout jusqu'au prochain qdans un "tampon" dans a.
  • ma crée une marque sur la ligne actuelle.
  • :5<CR> va au groupe suivant.
  • y$ tire le reste de la ligne.
  • 'a revient à la marque définie plus tôt.
  • $p pâtes en fin de ligne.
  • :5<CR> revient à la première ligne du deuxième groupe.
  • dd le supprime.
  • 'a revient à la marque.
  • jq descend d'une ligne et arrête l'enregistrement.
  • 3@a répète l'action pour chaque ligne (3 dans mon cas)

1
Vous devez appuyer [Enter]après les :5deux saisies ou cela ne fonctionnera pas.
Shawn J. Goff

1
Je suis sur gvim. Existe-t-il un moyen de copier-coller cette commande dans GVim? ^ V colle automatiquement en mode insertion (ce qui est logique, c'est ce que les gens veulent généralement) même si je suis actuellement en mode normal (?). J'ai essayé :norm qama:5<CR>y$'a$p:5<CR>dd'ajq3@amais cela semble s'exécuter seulement q.
ThiefMaster

1
ThiefMaster: Essayez :let @a="ma:5^My$'a$p:5^Mdd'aj" | normal 4@a, où les ^Mcaractères sont saisis en appuyant sur CTRL-V puis sur Entrée.
rampion

8

Comme mentionné ailleurs, la sélection de blocs est la voie à suivre. Mais vous pouvez également utiliser n'importe quelle variante de:

:!tail -n -6 % | paste -d '\0' % - | head -n 5

Cette méthode repose sur la ligne de commande UNIX. L' pasteutilitaire a été créé pour gérer ce type de fusion de lignes.

PASTE(1)                  BSD General Commands Manual                 PASTE(1)

NAME
     paste -- merge corresponding or subsequent lines of files

SYNOPSIS
     paste [-s] [-d list] file ...

DESCRIPTION
     The paste utility concatenates the corresponding lines of the given input files, replacing all but the last file's newline characters with a single tab character,
     and writes the resulting lines to standard output.  If end-of-file is reached on an input file while other input files still contain data, the file is treated as if
     it were an endless source of empty lines.

Utiliser la sélection de blocs n'est pas la seule solution. Ce n'est pas non plus le plus simple. Le paste -dcomportement souhaité ( -like) peut être mis en œuvre au moyen de la commande Vim courte, comme indiqué dans ma réponse .
ib.

3
En plus de cela, je suis sur Windows, donc cette solution impliquerait d'ouvrir une connexion SSH sur ma machine Linux et de la coller de l'éditeur dans le terminal et vice-versa.
ThiefMaster

3

Les exemples de données sont les mêmes que ceux de rampion.

:1,4s/$/\=getline(line('.')+4)/ | 5,8d

3

Je ne pense pas que ce soit trop compliqué. Je voudrais juste mettre virtualedit sur
( :set virtualedit=all)
Sélectionnez le bloc 123 et tous ci-dessous.
Mettez-le après la première colonne:

abc    123
def    45
...    ...

et supprimez l'espace multiple entre 1 espace:

:%s/\s\{2,}/ /g

La question ne demande en fait aucun espace, je ferais quelque chose comme gvV:'<,'>s/\s+//g(vim devrait automatiquement insérer le '<,'>pour vous afin que vous n'ayez pas besoin de le taper manuellement).
Ben

2

J'utiliserais des répétitions complexes :)

Compte tenu de ceci:

aaa
bbb
ccc

AAA
BBB
CCC

Avec le curseur sur la première ligne, appuyez sur ce qui suit:

qa}jdd''pkJxjq

puis appuyez sur @a(et vous pouvez ensuite utiliser@@ ) autant de fois que nécessaire.

Vous devriez vous retrouver avec:

aaaAAA
bbbBBB
cccCCC

(Plus une nouvelle ligne.)

Explication:

  • qa commence à enregistrer une répétition complexe dans a

  • } saute à la prochaine ligne vide

  • jdd supprime la ligne suivante

  • '' revient à la position avant le dernier saut

  • p collez la ligne supprimée sous la ligne actuelle

  • kJ ajouter la ligne actuelle à la fin de la précédente

  • xsupprimer l'espace qui Js'ajoute entre les lignes combinées; vous pouvez omettre cela si vous voulez de l'espace

  • j aller à la ligne suivante

  • q terminer l'enregistrement répétitif complexe

Après cela, vous utiliseriez @apour exécuter la répétition complexe stockée dans a, puis vous pouvez utiliser @@pour réexécuter la dernière répétition complexe exécutée.


1

Il peut y avoir de nombreuses façons d'y parvenir. Je fusionnerai deux blocs de texte en utilisant l'une des deux méthodes suivantes.

supposons que le premier bloc se trouve à la ligne 1 et que le deuxième bloc commence à la ligne 10 avec la position initiale du curseur à la ligne numéro 1.

(\ n signifie appuyer sur la touche Entrée.)

1. abc
   def
   ghi        

10. 123
    456
    789

avec une macro en utilisant les commandes: copier, coller et joindre.

qaqqa: + 9y \ npkJjq2 @ a10G3dd

avec une macro en utilisant les commandes déplacer une ligne au nième numéro de ligne et rejoindre.

qcqqc: 10 m. \ nkJjq2 @ c

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.