Gabungkan beberapa baris (dua blok) di Vim

323

Saya ingin menggabungkan dua blok garis di Vim, yaitu mengambil garis n..mdan menambahkannya ke garis a..b. Jika Anda lebih suka penjelasan kodesemu:[a[i] + b[i] for i in min(len(a), len(b))]

Contoh:

abc
def
...

123
45
...

harus menjadi

abc123
def45

Apakah ada cara yang bagus untuk melakukan ini tanpa melakukan copy & paste secara manual?

Pencuri
sumber
Jadi ... Anda ingin bergabung dengan jalur bolak-balik? Artinya, Anda ingin bergabung xdengan bergabung x+2?
larsks
1
Tidak, saya punya dua blok terpisah. Pseudocode-ish:[a[i] + b[i] for i in min(len(a), len(b))]
ThiefMaster
2
Lihat pertanyaan serupa (dan jawab!) Di sini
NWS

Jawaban:

880

Anda tentu dapat melakukan semua ini dengan satu salin / tempel (menggunakan pemilihan blok-mode), tapi saya rasa bukan itu yang Anda inginkan.

Jika Anda ingin melakukan ini hanya dengan perintah Ex

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

akan berubah

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

ke

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

UPDATE: Sebuah jawaban dengan banyak upvotes ini layak mendapat penjelasan yang lebih menyeluruh.

Di Vim, Anda bisa menggunakan karakter pipa ( |) untuk mengaitkan beberapa perintah Ex, sehingga di atas sama dengan

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

Banyak perintah Ex menerima rentang baris sebagai argumen awalan - dalam kasus di atas 5,8sebelum deldan 1,4sebelum s///menentukan baris mana perintah beroperasi.

delmenghapus garis yang diberikan. Itu dapat mengambil argumen register, tetapi ketika seseorang tidak diberikan, ia membuang baris ke register yang tidak disebutkan namanya @",, seperti menghapus dalam mode normal. let l=split(@")kemudian memecah baris yang dihapus menjadi daftar, menggunakan pembatas default: whitespace. Untuk bekerja dengan baik pada input yang memiliki spasi putih di baris yang dihapus, seperti:

more than 
hour 
our 
never 
ever
after
work is
over
~

kami perlu menentukan pembatas yang berbeda, untuk mencegah "pekerjaan" dari terbelah menjadi dua daftar elemen: let l=split(@","\n").

Akhirnya, dalam substitusi s/$/\=remove(l,0)/, kami mengganti akhir setiap baris ( $) dengan nilai ekspresi remove(l,0). remove(l,0)mengubah daftar l, menghapus dan mengembalikan elemen pertamanya. Ini memungkinkan kita mengganti baris yang dihapus sesuai urutan kita membacanya. Kami malah bisa mengganti baris yang dihapus dalam urutan terbalik dengan menggunakan remove(l,-1).

rampion
sumber
1
hmm ... Saya hanya perlu menekan enter sekali. Dan itu tidak akan memasukkan spasi di antara kedua bagian. Jika ada beberapa spasi tambahan pada garis (seperti pada contoh "kerjakan"), itu masih ada. Anda dapat menyingkirkan ruang trailing menggunakan s/\s*$/bukan s/$/.
rampion
1
Terima kasih untuk penjelasannya. :sil5,8del | let l=split(@") | sil1,4s/$/\=remove(l,0)/ | call histdel("/", -1) | nohlstampaknya lebih baik karena membersihkan riwayat pencarian setelah berjalan. Dan itu tidak menunjukkan pesan "x lebih / kurang baris" yang mengharuskan saya untuk menekan enter.
ThiefMaster
11
Jika Anda ingin referensi vim penuh untuk jawaban yang: :help range, :help :d, :help :let, :help split(), :help :s, :help :s\=, :help remove().
Benoit
16
Memastikan orang-orang seperti Anda ingin memposting jawaban seperti ini adalah mengapa saya menjadi moderator. Pertunjukan yang bagus :)
Tim Post
1
Ada masalah jika tidak ada spasi putih setelah 4 kalimat pertama.
Reman
58

Perintah Ex elegan dan ringkas memecahkan masalah ini dapat diperoleh dengan menggabungkan :global, :movedan :joinperintah. Dengan asumsi bahwa blok pertama dari garis dimulai pada baris pertama dari buffer, dan bahwa kursor terletak pada baris segera sebelum baris pertama dari blok kedua, perintahnya adalah sebagai berikut.

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

Untuk penjelasan rinci tentang teknik ini, lihat jawaban saya untuk pertanyaan serupa " Vim paste -d '' perilaku di luar kotak?

ib.
sumber
E16: Invalid Range- tapi tetap berhasil. Saat melepas 1,itu berfungsi tanpa kesalahan.
ThiefMaster
Dan sayang sekali Anda tidak dapat menerima lebih dari satu jawaban - jika tidak, jawaban Anda juga akan memiliki tanda hijau!
ThiefMaster
3
Sangat bagus! Saya tidak tahu tentang :movedan :join!, atau apa yang ''dimaksud dengan argumen rentang ( :help '') dan apa +dan -artinya sebagai pengubah kisaran ( :help range). Terima kasih!
rampion
@ThiefMaster: Perintah ini dirancang untuk digunakan ketika dua rentang garis untuk bergabung berdekatan satu sama lain, sehingga kursor awalnya terletak di baris terakhir dari blok pertama yang segera mendahului baris pertama dari blok kedua. (Lihat jawaban dan pertanyaan yang ditautkan.) Perintah ini berfungsi seperti yang dimaksudkan bahkan jika jumlah baris dalam blok berbeda, meskipun memberikan pesan kesalahan dalam kasus itu. Seseorang dapat menekan pesan kesalahan dengan menambahkan sil!ke perintah.
ib.
2
@ib .: Saya pikir itu akan menjadi ide yang bagus untuk memasukkan penjelasan terperinci ke dalam jawaban ini juga.
ThiefMaster
44

Untuk bergabung dengan blok garis, Anda harus melakukan langkah-langkah berikut:

  1. Pergi ke baris ketiga: jj
  2. Masuk ke mode blok visual: CTRL-v
  3. Jangkar kursor ke ujung garis (penting untuk garis dengan panjang yang berbeda): $
  4. Pergi ke akhir: CTRL-END
  5. Potong blok: x
  6. Pergi ke akhir baris pertama: kk$
  7. Tempel blok di sini: p

Gerakannya bukan yang terbaik (saya bukan ahli), tapi itu bekerja seperti yang Anda inginkan. Semoga akan ada versi yang lebih pendek.

Berikut adalah prasyarat agar teknik ini bekerja dengan baik:

  • Semua baris blok awal (dalam contoh pada pertanyaan abcdan def) memiliki panjang XOR yang sama
  • baris pertama dari blok awal adalah yang terpanjang, dan Anda tidak peduli dengan ruang tambahan di antaranya) XOR
  • Baris pertama dari blok awal bukanlah yang terpanjang, dan Anda menambahkan ruang tambahan sampai akhir.
mliebelt
sumber
Woah, itu menarik! Saya tidak akan pernah berpikir bahwa itu akan bekerja seperti itu.
voithos
13
Ini berfungsi seperti yang diinginkan hanya karena abcdan defpanjangnya sama. Pilihan blok akan menjaga indentasi dari teks yang dihapus, jadi jika kursor berada pada garis pendek saat meletakkan teks, garis dimasukkan di antara huruf-huruf pada yang lebih panjang - dan spasi ditambahkan ke yang lebih pendek jika kursor berada pada garis yang lebih panjang. satu.
Izkata
Memang ... Apakah ada cara untuk melakukannya dengan benar menggunakan block copy & paste? Bagaimanapun, ini adalah cara termudah untuk melakukannya (terutama jika Anda tidak dapat mencari cara yang lebih rumit di sini untuk beberapa alasan).
ThiefMaster
2
Seperti @Izkata mengatakan Anda akan mengalami masalah dengan teks yang dimasukkan di antara baris yang lebih panjang. Untuk mengatasinya, tambahkan saja lebih banyak spasi di akhir baris pertama untuk menjadikannya yang terpanjang dan kemudian tempel blok teks Anda. Setelah selesai, mengompresi banyak ruang menjadi satu adalah sesederhana:%s/ \+/ /g
Khaja Minhajuddin
1
set ve=allseharusnya membantu, lihat vimdoc.sourceforge.net/htmldoc/options.html#'virtualedit '
Ben
19

Begini cara saya melakukannya (dengan kursor di baris pertama):

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

Anda perlu tahu dua hal:

  • Nomor baris tempat baris pertama grup kedua dimulai (5 dalam kasus saya), dan
  • jumlah baris dalam setiap grup (3 dalam contoh saya).

Inilah yang terjadi:

  • qamencatat semuanya hingga selanjutnya qmenjadi "buffer" di a.
  • ma menciptakan tanda pada baris saat ini.
  • :5<CR> pergi ke grup berikutnya.
  • y$ menarik sisa garis.
  • 'a kembali ke tanda, atur lebih awal.
  • $p pasta di akhir baris.
  • :5<CR> kembali ke baris pertama grup kedua.
  • dd menghapusnya.
  • 'a kembali ke sasaran.
  • jq turun satu baris, dan berhenti merekam.
  • 3@a mengulangi tindakan untuk setiap baris (3 dalam kasus saya)
Kenneth Ballenegger
sumber
1
Anda harus menekan [Enter]setelah :5kedua kali Anda mengetiknya atau ini tidak akan berhasil.
Shawn J. Goff
1
Saya sedang gvim. Apakah ada cara untuk menyalin & menempelkan perintah itu di GVim? ^ V secara otomatis menempel dalam mode insert (yang masuk akal, itulah yang biasanya orang inginkan) bahkan jika saya saat ini dalam mode normal (?). Saya mencoba :norm qama:5<CR>y$'a$p:5<CR>dd'ajq3@atetapi yang tampaknya hanya mengeksekusi q.
ThiefMaster
1
ThiefMaster: Coba :let @a="ma:5^My$'a$p:5^Mdd'aj" | normal 4@a, di mana ^Mkarakter diketik dengan menekan CTRL-V lalu Enter.
rampion
8

Seperti disebutkan di tempat lain, pemilihan blok adalah jalan yang harus ditempuh. Tetapi Anda juga dapat menggunakan varian apa pun dari:

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

Metode ini bergantung pada baris perintah UNIX. The pasteutilitas diciptakan untuk menangani semacam ini baris penggabungan.

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.
kevinlawler
sumber
Menggunakan pemilihan blok bukan satu-satunya cara untuk pergi. Juga bukan yang paling sederhana. paste -dPerilaku yang diinginkan ( seperti) dapat diimplementasikan dengan menggunakan perintah Vim pendek, seperti yang ditunjukkan dalam jawaban saya .
ib.
3
Selain itu, saya di windows sehingga solusi akan melibatkan membuka koneksi SSH ke mesin linux saya dan menempelkan dari editor ke terminal dan kembali.
ThiefMaster
3

Data sampel sama dengan rampion.

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

Saya tidak akan berpikir membuatnya terlalu rumit. Saya hanya akan mengatur virtualedit pada
( :set virtualedit=all)
Pilih blok 123 dan semuanya di bawah.
Taruh setelah kolom pertama:

abc    123
def    45
...    ...

dan hapus beberapa ruang antara hingga 1 ruang:

:%s/\s\{2,}/ /g
Merebut kembali
sumber
Pertanyaannya sebenarnya tidak meminta spasi, saya akan melakukan sesuatu seperti gvV:'<,'>s/\s+//g(vim harus secara otomatis memasukkan '<,'>untuk Anda sehingga Anda tidak perlu mengetikkannya secara manual).
Ben
2

Saya akan menggunakan pengulangan kompleks :)

Mengingat ini:

aaa
bbb
ccc

AAA
BBB
CCC

Dengan kursor di baris pertama, tekan berikut ini:

qa}jdd''pkJxjq

dan kemudian tekan @a(dan selanjutnya Anda dapat menggunakan @@) sebanyak yang diperlukan.

Anda harus berakhir dengan:

aaaAAA
bbbBBB
cccCCC

(Ditambah baris baru.)

Penjelasan:

  • qa mulai merekam pengulangan yang kompleks di a

  • } melompat ke baris kosong berikutnya

  • jdd menghapus baris berikutnya

  • '' kembali ke posisi sebelum lompatan terakhir

  • p rekatkan baris yang dihapus di bawah yang saat ini

  • kJ tambahkan baris saat ini ke akhir yang sebelumnya

  • xhapus spasi yang Jmenambahkan di antara garis-garis gabungan; Anda dapat menghilangkan ini jika Anda menginginkan ruang

  • j pergi ke baris berikutnya

  • q akhiri rekaman berulang yang kompleks

Setelah itu Anda akan menggunakan @auntuk menjalankan pengulangan kompleks yang disimpan a, dan kemudian Anda dapat menggunakannya @@untuk menjalankan kembali pengulangan kompleks yang terakhir dijalankan.

Gerardo Marset
sumber
1

Ada banyak cara untuk mencapai hal ini. Saya akan menggabungkan dua blok teks menggunakan salah satu dari dua metode berikut.

misalkan blok pertama berada pada baris 1 dan blok ke-2 dimulai dari baris 10 dengan posisi awal kursor pada baris nomor 1.

(\ n berarti menekan tombol enter.)

1. abc
   def
   ghi        

10. 123
    456
    789

dengan makro menggunakan perintah: salin, tempel dan gabung.

qaqqa: + 9y \ npkJjq2 @ a10G3dd

dengan makro menggunakan perintah pindahkan baris di nomor baris ke-n dan gabung.

qcqqc: 10m. \ nkJjq2 @ c

dvk317960
sumber