9

Is it possible to reuse the range of ex commands in VIM?

As an example, I can write (copy) lines 4 to 10 from my current file to a new file using the following command:

:4,10w foo/bar.txt

But what I really want to do is move the lines to a new file. I can do this like so:

:4,10w foo/bar.txt
:4,10d

But it's a bit annoying to have to type 4,10 both times.

So I have two questions:

  1. Generally, Is there a way to reference the previously used range in ex commands?
  2. Specifically, if there is not a way to do (1), is there any easier way to cut and paste a number of lines from one file into a new one.
latentflip
  • 1,843
  • 3
  • 19
  • 22

8 Answers8

5

I usually use cat to do this:

:4,10!cat > foo/bar.txt

This works because you're piping the lines through cat and replacing them with the resulting output, which is nothing. And of course you can append to the end of an existing file by doing >> instead of >.

Ben Rady
  • 136
  • 1
4

I am unaware of an answer to (1), but to answer (2), there are a number of different ways of doing it that don't require reselecting the lines by hand. In visual mode this will work:

4GV10G
:w foo/bar.txt
gvd

because gv reselects the previous selection, which is almost what you want, without using an ex range.

But you could just turn the problem on its head, and try:

:4,10d
:sp foo/bar.txt
pZZ

to cut, then paste into a new file, then close it.

moopet
  • 6,014
  • 1
  • 29
  • 36
3

Other than using the Vim history (:Cursor Up, q:) and removing the previous command so that just the range is kept, there's no way to re-use the last range, no magic variable.

If I used this move lines combination more often, I would write a custom command for it:

command! -bang -range -nargs=1 -complete=file MoveWrite <line1>,<line2>write<bang> <args> | <line1>,<line2>delete _

You need to specify the range only once and save typing.

You can write something like this for other combinations, too. The main challenge is specifying all the command attributes (bang, range, completion), and, later, remembering the custom command name.

Ingo Karkat
  • 167,457
  • 16
  • 250
  • 324
2

Generally, what I do is delete the lines from the one file, switch to the other file, and paste.

Also, I generally use marks. Instead of typing the actual numbers, I hit mb to mark the beginning line, then go to the end line and hit d'b to delete back to the line marked as b. But you can use mb to mark a begin line, and me to mark an end line, then run an ex command:

:'b,'e w somefile.txt<Enter>

Of course you can use any letters from a through z for your marks; I usually use b and e but you can use what you like.

How I would move the lines:

m'b
<navigate to end line>
d'b
:n somefile.txt<Enter>
p
Ctrl+^

Ctrl+^ switches from the current open file to the previous open file. (You could also just open a pane and switch panes, if you prefer. Panes don't work in plain vi but do work in vim.)

The above assumes that you have set the autowrite option on. With autowrite, the :n command and Ctrl+^ both just write the current file and then switch files, instead of complaining that the file has been changed without you saving it. You can also do the above and just explicitly write the file before using :n or Ctrl+^.

By the way, I use Ctrl+^ so much that I mapped it onto K. Easier to type, but I got in that habit long ago when I used to have to sometimes use a dumb terminal that couldn't type Ctrl+^.

By the way, when you delete lines, they go into the "unnamed buffer". In vim, the unnamed buffer is preserved when you switch files. In original vi, the unnamed buffer is cleared. So the above won't work with old vi. You can make it work by deleting into a named buffer, then pasting from the named buffer; that works in any version of vi.

m'b
<navigate to end line>
"ad'b
:n somefile.txt<Enter>
"ap
Ctrl+^

The above deletes into the buffer named a, then pastes from a in the other file. This does work in vim of course; it's just that you don't need it.

steveha
  • 74,789
  • 21
  • 92
  • 117
2

Here's a command-line mapping that achieves this. I've bound it to CTRL-G CTRL-U, since it performs a similar action as CTRL-U. (But you can change that, of course!)

" c_CTRL-G_CTRL-U   Remove all characters between the cursor position and
"           the closest previous |:range| given to a command. When
"           directly after a range, remove it.
"           Useful to repeat a recalled command line with the same
"           range, but a different command.
let s:singleRangeExpr = '\%(\d\+\|[.$%]\|''\S\|\\[/?&]\|/[^/]*/\|?[^?]*?\)\%([+-]\d*\)\?'
let s:rangeExpr = s:singleRangeExpr.'\%([,;]'.s:singleRangeExpr.'\)\?'
let s:upToRangeExpr = '^\%(.*\\\@<!|\)\?\s*' . s:rangeExpr . '\ze\s*\h'
" Note: I didn't take over the handling of command prefixes (:verbose, :silent,
" etc.) to avoid making this overly complex.
function! s:RemoveAllButRange()
    let l:cmdlineBeforeCursor = strpart(getcmdline(), 0, getcmdpos() - 1)
    let l:cmdlineAfterCursor = strpart(getcmdline(), getcmdpos() - 1)

    let l:upToRange = matchstr(l:cmdlineBeforeCursor, s:upToRangeExpr)
    if empty(l:upToRange)
        return getcmdline()
    else
        call setcmdpos(strlen(l:upToRange) + 1)
        return l:upToRange . l:cmdlineAfterCursor
    endif
endfunction
cnoremap <C-g><C-u> <C-\>e(<SID>RemoveAllButRange())<CR>

as a plugin

My CmdlineSpecialEdits plugin has (among many others) this mapping as well.

Ingo Karkat
  • 167,457
  • 16
  • 250
  • 324
1

You can also do something like this to write the contents of the anonymous register to file2.txt

 :4,10d | :call writefile(split(@@, "\n", 1), 'file2.txt')
0

You can do the deleting first, and then open a new tab and paste the contents - so :4,10d, then :tabe foo/bar.txt, followed by p... does that sound better?

Ambidextrous
  • 810
  • 6
  • 14
0

In Vim 8 and NVIM 0.3.7 as of writing, you can actually edit your command list and hit enter to execute.

:4,10w foo/bar.txt
q:

q: is to enter interactive ex command

Once you open the interactive command list, you can then edit it and press enter to execute.

I love moopet's answer though, it's efficient.

chriz
  • 1,826
  • 2
  • 24
  • 28