33

I know I can join lines in Vim with J and several variants thereof (gJ, etc.); J will "Remove the indent and insert up to two spaces".

But is there also a straightforward way to insert another character instead of the two spaces? For example a comma (,) or comma & space (,)?

Given:

8
9
10
32

I'd like to end up with:

8, 9, 10, 32

or:

8,9,10,32
3

7 Answers 7

6

I wrote a Join plugin, can do what you want, and a lot more.

Except for all features provided by the build-in :join command, Join can:

  • Join lines with separator (string)
  • Join lines with or without trimming the leading/trailing whitespaces
  • Join lines with negative count (backwards join)
  • Join lines in reverse
  • Join lines and keep joined lines (without removing joined lines)
  • Join lines with any combinations of above options

more details and screenshots:

https://github.com/sk1418/Join

38

A simple way is this: simply select your lines (all but the last one) - or use % - and run:

:'<,'>s/\n/,/

or

:'<,'>s/\n/, /

(where, of course, the '<,'> part was already inserted after : by Vim, to target the selection)


(2nd) Update:

Building on the above (and Sato Katsura's comment), here's a possible "interactive join" implementation, with count and optional repeat support:

" ================ script ===============================================
" interactive 'J', 'gJ' replacement with optional 'vim-repeat' support
" The last used separator is automatically reused as:
" a. default choice
" b. when repeating (=> non-interactive repeats: same range, same separator)
let g:last_join_separator = " "
function! s:interactiveJoin(use_last_sep,...) range
    if (a:use_last_sep == 0) "interactive, ask for separator to use
        call inputsave()
        echohl Question
        let l:sep = input("Separator:", g:last_join_separator)
        echohl None
        call inputrestore()
        redraw!
        let g:last_join_separator = l:sep "update last separator value
    else "non-interactive (when repeating with '.')
        let l:sep = g:last_join_separator
    endif
    if (a:0 == 0) "with no argument, remove indentation *and trailing spaces*
        let l:subst = 's/\s*\n\+\s*/\=' . "'" . l:sep . "'/"
    else " don't remove indentation or trailing spaces (act like 'gJ')
        let l:subst = 's/\n\+/\=' . "'" . l:sep . "'/"
    endif
    if a:firstline < a:lastline "join given range
        execute a:firstline . ',' . (a:lastline - 1) . l:subst
        let l:count = a:lastline - a:firstline + 1 "default count for repeat
    else "or join only with next line
        execute l:subst
        let l:count = 1 "default count for repeat
    endif
    "make command repeatable
    "(with the tpope/vim-repeat plugin: optional, recommended)
    if (a:0 == 0)
        silent! call repeat#set("\<Plug>(repeatJoin)", l:count)
    else
        silent! call repeat#set("\<Plug>(repeatGJoin)", l:count)
    endif
endfunction

noremap <silent> <Plug>(interactiveJoin)  :call <SID>interactiveJoin(0)<CR>
noremap <silent> <Plug>(interactiveGJoin) :call <SID>interactiveJoin(0,'g')<CR>
noremap <silent> <Plug>(repeatJoin)       :call <SID>interactiveJoin(1)<CR>
noremap <silent> <Plug>(repeatGJoin)      :call <SID>interactiveJoin(1,'g')<CR>

And an actual mapping:

"================= vimrc ================================================
nmap J <Plug>(interactiveJoin)
xmap J <Plug>(interactiveJoin)
nmap gJ <Plug>(interactiveGJoin)
xmap gJ <Plug>(interactiveGJoin)

This is kinda(*) like J, but interactive - it will prompt for the separator string. The default string is a space - so, for example, to join lines with no separator, hit Backspace when prompted, to remove the default space character, and Enter to accept the (now) empty separator. Count, e.g. 3J, also works. If tpope/vim-repeat plugin is installed, repeating with '.' will also work, reusing the last separator and (if not changed - e.g. 10.) the last count or visual line range.


(*) It's not exactly like J,though: while it will remove indentation, it won't check for .!? (end of phrase) to insert 2 spaces instead of one, or insert a space only if it's missing (it's hard to do something like this, since the separator string can be anything now). It will also remove trailing spaces (makes more sense).

I think this might be a nice way to overload the limited operators letter-space :)

Well, technically J is not quite an operator, but close to one - for example, you can't do Jaw, to join "a word".

(suggestions are welcome)

enter image description here

8
  • 3
    Better yet: :'<,'>-1s/\n/, / for marked lines, or :1,$-1s/\n/, / for the entire file. Aug 3, 2015 at 8:33
  • Indeed, that's even better :)
    – VanLaser
    Aug 3, 2015 at 8:35
  • @SatoKatsura can you explain the -1?
    – alxndr
    Aug 3, 2015 at 18:12
  • 1
    @alxndr - it's used to skip the last line in the range/file: e.g. in the OP example, you don't want a comma after 32, and also you don't want to join it with the next line, if it exists. You can even use '<,'>-s/\n/, / or 1,$-s/\n/, / as - is the same as -1.
    – VanLaser
    Aug 3, 2015 at 20:41
  • 1
    If l:sep is an &, then your replacement will work incorrectly, since & is magic in the replacement. Might be better to use the \= to use a literal string -- let l:subst = 's/\n\+/\=' . "'" . l:sep . "'/" -- instead of trying to handle escaping.
    – jamessan
    Aug 5, 2015 at 20:29
7

Here is example using external command:

:%!paste -sd,

where %! replaces current buffer (content of the file) with the command: paste -sd, (parameters -s is used to join multiple lines and -d sets the delimiter character to ,).

1
6

Just in case someone doesn't like typing Regex and memorizing more Plugin Ex Commands:

Visual-block Append

Visual-block Append is available in the Visual Block mode (<C-v>) and can do a block Append (and block Insert for prepend). (ref: :help v_b_A, :help v_b_I)

And if the last motion in Block Visual Mode is $, the append will be done at the end of all selections; or else spaces will be inserted before the text you appended (imagine aligning closing ) at the end of several method calls in code).

With sample text below and cursor on 8:

8
9
10
12

<C-v>}$A,<C-c>. After going back to normal mode, we can see , is appended to all selected lines. (Try without $)

Then do gv to re-select the area and J or gJ. We have a trailing ,: 8,9,10,12, and $x we're done.

Use Insert for No Trailing ,

Let's undo our text (be it uu..u or a fancy :ea 1d<cr>:lat 1), and try Visual Insert:

j<C-v>}I, <C-c> and then we can join the lines with gJ for no spaces.

Wait wait wait, something is missing. How to re-select the whole "array"? This time we didn't select the first line... so gv doesn't help.

-- Or it may help, gvok is re-select (gv), opposite corner and k up.

P.S. My pinky isn't quick and accurate enough to type /\ for /\n, so I like combining Vim built-in operations and movements to avoid using Regex. However if I do this frequent enough, using a mapping -- maybe ones that map to plugins should be better.

1
  • Great answer. I had to change <C-c> to ESC though. Aug 24, 2022 at 3:02
2

You can record a macro to join lines with a comma. This is useful if it's a rare or one-off task, and you can't remember how to write a substitute command and regular expression to perform the task, but you do remember how to record and run a macro.

To join lines with a comma, type qjgJi,Escq to record the macro in register j. Then you can run the macro with @j. You can re-run the macro with @@, which saves on typing when running it multiple times in a row—you can even just hold down @ until you get near the end of the lines you want to join. You can also use a numeric prefix on @j or @@ to repeat it a set number of times. This leaves leading and trailing space intact, so there will be spaces before or after the comma if your original text had spaces there.

To join with a comma and a space, insert Space between the , and Esc when recording the macro.

You can also use J instead of gJ when recording the macro, which will usually join lines with a comma and a space. However, if the current line has trailing whitespace, the comma will appear after the whitespace without a space following it. Also, it does not include the space if the next line starts with ), and it puts two spaces after the comma if joinspaces is set and the current line ends with ., !, or ?. These caveats are described under :help J.

2

A much much simpler way without plugins or external commands to remap join (J) from a space to whatever character you like. For instance, to join with a comma and a space:

:nmap J :s/\n/, /^M

For the special character newline at the end (^M) press Ctrl-V and then Return/Enter. Same goes if you want to replace with a tab (^I), type Ctrl-I then Tab.

:nmap J :s/\n/^I/^M

What this is doing is replacing a newline with a tab on the current line and remapping that behavior to the J character.

This also works with combinations, as in 4J to join the next 4 lines.

2

To get around this limitation we can resolve this problem by splitting it for 2 separate simple problems:

  1. Add separator at the end of each line with one of command:

  2. join lines with %join

This solution is a little bit easier to come up with than %s/\n/,/.

1
  • 1
    Welcome to Vi and Vim! I like your use of ex idioms; it's a fast and flexible approach. I also appreciate $ instead of \n—not all files use \n as the eol, so this is actually better than :%substitute/\n/,!
    – D. Ben Knoble
    Nov 30, 2020 at 17:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.