Jak połączyć wszystkie linie razem, który pasujący wzór?

12

Chciałbym łączyć linie tylko dla linii, które mają określony wzór (np. ;), Jednak przy użyciu g/;/jnie działa zgodnie z oczekiwaniami, chyba że zostanie wywołany kilka razy.

Na przykład następująca treść:

a
1;
2;
3;
4;
5;
b
6;
7;
8;
9;
c

przy użyciu: :g/;/jdane wyjściowe to:

a
1; 2;
3; 4;
5; b
6; 7;
8; 9;
c

lub :g/;/-jdaje:

a 1; 2; 3; 4; 5;
b 6; 7; 8; 9;
c

Podobnie jest z: :g/;\_.\{-};/j.

Moje oczekiwane wyniki to:

a 
1; 2; 3; 4; 5;
b
6; 7; 8; 9;
c

lub coś podobnego, więc wszystkie linie zawierające wzór są ze sobą połączone.

Jak można to osiągnąć?

kenorb
źródło
3
FWIW, :g/;/jnie działa, ponieważ odbywa się to w dwóch przebiegach: najpierw skanowany jest bufor, a następnie polecenie jest stosowane do pasujących linii.
romainl,

Odpowiedzi:

12

Możliwe wyjaśnienie problemu

Myślę, że powodem, dlaczego :g/;/jnie działa dlatego, że :gkomenda działa z algorytmem 2-pass:

  • podczas pierwszego przejścia zaznacza linie zawierające wzór ;
  • podczas drugiego przejścia działa na zaznaczonych liniach

Podczas drugiego przejścia :głączy linię 1;z linią, 2;ponieważ 1;zostało zaznaczone podczas pierwszego przejścia. Podejrzewam jednak (nie jestem pewien), że nie łączy 1; 2;się, 3;ponieważ linia 2;już nie istnieje, jej zawartość została scalona z linią, 1;która została już przetworzona.

Więc :gszuka następnej linii, która została zaznaczona podczas pierwszego przejścia ( 3;) i łączy ją z następną ( 4;). Następnie powtarza się problem, to nie może przyłączyć 3; 4;się 5;, ponieważ linia 4;już nie istnieje.

Rozwiązanie 1 (z vimscript)

Być może możesz wywołać funkcję za każdym razem, gdy ;zostanie znaleziony wiersz zawierający, aby sprawdzić, czy poprzedni wiersz zawiera również średnik:

function! JoinLines()
    if getline(line('.')-1) =~ ';'
        .-1join
    endif
endfunction

Następnie użyj następującego polecenia globalnego:

:g/;/call JoinLines()

Lub bez funkcji:

:g/;/if getline(line('.')-1) =~ ';' | -j | endif

Rozwiązanie 2 (bez vimscript)

:g/;/.,/^[^;]*$/-1j

Ilekroć polecenie globalne :gznajdzie wzorzec ;, wykonuje polecenie: .,/^[^;]*$/-1j

Można go podzielić w następujący sposób:

:g/pattern/a,bj

Gdzie :

pattern = ;
a       = .           = number of current line
b       = /^[^;]*$/-1 = number of next line without any semicolon minus one

b można podzielić dalej w następujący sposób:

/    = look for the number of the next line matching the following pattern
^    = a beginning of line
[^;] = then any character except a semicolon
 *   = the last character can be repeated 0 or more times
 $   = an end of line
 /   = end of pattern
 -1  = removes one to the number you just got

jto skrócona forma polecenia Ex, :joinktóra podobnie jak większość innych poleceń Ex może być poprzedzona zakresem.
Tutaj poprzedza go zakres: .,/^[^;]*$/-1( a,b)
Zakres ma postać a,bgdzie ai bsą zwykle 2 numerami linii, i pozwala ci operować na grupie linii, których liczba jest pomiędzy ai bzamiast tylko jednej.

Tak więc jpolecenie łączy wszystkie linie między bieżącym ( a) a następnym, który nie zawiera żadnego średnika minus jeden ( b).

Aby uzyskać więcej informacji, zobacz:

:help :global
:help :join
:help :range
saginaw
źródło
2

Cały czas robię podobne łączenie za pomocą globalnego wyszukiwania i zastępuję:

s /; \ n /; /

\n pasuje do nowej linii.

Aby znaleźć i usunąć puste linie:

s / ^ $ \ n //

Nie jestem pewien dlaczego, ale jeśli chcesz wstawić nową linię, musisz użyć \r

rlh100
źródło
ssam będzie działać tylko na jednej linii, aby to globalny, trzeba użyć %s, ale to będzie dołączyć niemal wszystkie linie, w tym spoza ;linii
kenorb
2
@kenorb Ehm nie, myślę, że możesz użyć :spolecenia dokładnie do tego, co chcesz. Myślę, że to %s/;\n\(.*;\)\@=/;/robi to, czego potrzebujesz.
Christian Brabandt