Reusing the previous range in ex commands in VIM - vim

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:
Generally, Is there a way to reference the previously used range in ex commands?
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.

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 >.

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.

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.

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.

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.

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')

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?

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.

Related

In vim toggle case of all characters in a text file with a single command

In Vim I need to convert all lowercase to uppercase and all uppercase to lowercase with a single command. So if my text file looks like this..
Hello World
.. it needs to be toggled to look like this..
hELLO wORLD
I know :%s/[a-z]/\U&/g will change all lowercase to uppercase and that :%s/[A-Z]/\L&/g will change all uppercase to lowercase. But how would I write that to do both at the same time?
In addition I know if my cursor is at the top of the file VG~ will toggle case everything but that's not the answer I need. Thank you.
<Esc>1GVG~
Explanation:
<Esc> — return to Normal mode; just in case we're in Insert mode or Command line
1G — jump to the 1st line
V — start Visual mode
G — jump to the last line extending selection
~ — toggle case in the selection
Or
<Esc>1Gg~G
g~<motion> — change case during motion; the motion is G (jump to last line)
Docs: http://vimdoc.sourceforge.net/htmldoc/change.html#~
Looks like you already know everything you need. ggVG~ marks all your code and toggles the case. If you want a single command you can either use:
:nnoremap <keybinding> ggVG~
or use this function, which does the same, but keeps your current position in the file:
function ToggleCase()
exec "normal! mqHmw"
exec "normal! ggVG~"
exec "normal! 'wzt`q"
endfunction
command ToggleCase silent call ToggleCase()
the first and last exec mark your position in the file and restore them, after the case toggling. See: :h marks
type :ToggleCase to use the function. Of cause you can bind this to a keybinding as well.
:nnoremap <keybinding> :ToggleCase<cr>
Since you mentioned using a single command and you mentioned some :%s/.../ substitutions, I'll offer this one:
:%normal! g~~
This will run the g~~ command to switch case of a single line, for each line of the buffer.
One more way to accomplish this, if you're ok adopting a plug-in, is to use the kana/vim-textobj-entire plug-in for a text object for the entire buffer.
As the plug-in README.md file says:
Though these are trivial operations (e.g. ggVG), text object versions are more handy, because you do not have to be conscious of the cursor position (e.g. vae).
With this plug-in installed and enabled, you can switch case of the whole buffer with:
g~ae

Storing vim macro - how to store a move-to-next-line operation?

I want to store a macro that inserts a symbol at the beginning of a line and then moves to the next line (such that I can call multiple of them)
I made a recording of what I want and then used ctrl R ctrl R to paste it directly into my vimrc where it came out as;
nmap #c I%<80>kd^[
The problem is when I then call it, "%<80>kd" gets inserted, instead of just the "%" symbol.
----More info:
As you can probably guess I'm trying to insert comments, I'd also like to remove them with a similar #x invocation. I want to be able to call this on whatever number of lines so it needs to finish with a move-to-next-line operation. I know about the alternative approach of using visual mode and I% esc esc, but I find this easier - I used to have it working but have lost my vimrc and now don't seem to be able to re-create it alas...
Add the following lines to your .vimrc.
To insert % in the begining of each line & move to next line:
let #c="I%\<esc>+"
To delete first character from the beginning of each line & move to next line
let #d="0x+"
nmap #c I%<ESC>j seems to work for me. I am able to paste that using "cp (paste from register c).
To delete the first character, nmap #d ^xj.

How do I re-select a range in vim? [duplicate]

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:
Generally, Is there a way to reference the previously used range in ex commands?
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.
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 >.
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.
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.
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.
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.
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')
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?
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.

Custom vi command: How to insert current date with a variable's content?

I'm new to the vi editor and I would like to create a simple custom command in .vimrc that inserts something like 2012-03-13 22:21:17.0 +0100 / Daniel.
Actually, my command (in .vimrc) is as follows:
command! InsertTime :normal a<C-R>=strftime('%F %H:%M:%S.0 %z')<CR>
I also set a variable:
let myname="Daniel"
InsertTime inserts the date perfectly. But how can I concatenate it with the content of my variable?
To concatenate, vim scripts use . caracter. So try this one :
In vimrc:
let myname="Daniel"
command! InsertTime :normal a<C-R>=strftime('%F %H:%M:%S.0 %z') . "/" . myname<CR>
no tested there.
Since you said you're new to "vim" I am going to assume you don't know any of the things I'm about tell you. Mucho sorry if you already know them.
If you're going to do this a lot (insert the line "%F %H:%M:%S.0 %z / Daniel"), instead of defining a command, which you have to invoke with a :command_name, define a macro and/or an input macro that can be invoked with just two or three character.
To define an input macro, do the following at the ':' prompt, or add it to your $HOME/.exrc or $HOME/.vimrc file (without the preceding ':'):
:map <C-X><C-X> Go<ESC>!!date '+\%F \%H:\%M:\%S.0 \%z'<CR>A / Daniel<ESC>
Now when you're in "vi" (but not in input mode), typing control-Xcontrol-X will:
G go to last line in file; replace this with the "motion" keys sequence appropriate for your use (or nothing at all if you want to append the line right after the cursor)
o open a new line
<ESC> escape out of input mode
!!date ... invoke the date command, replace the current line with its stdout (output)
A append at the end of the line (now having the "date")
/ Dan... verbatim intput text
<ESC> escape out of input mode
control-Xcontrol-X can be some unusual sequence that you'd normally not use for anything, nor used by any "vi" operation that you might use. I use as the first character, because in "vi", decrements the next integer on the line after the cursor, if any. That is something I hardly ever do. I define my macros to be invoked with <C-X><C-B>, <C-X><C-D>, <C-X>s1, etc.
To create an input macro, well, that's another whole long subject, and I'm tired of typing today, so, another day. :)

How to diff two lines in an open file in vim?

I occasionally see very long lines in my code that I need to check if they are the same. Is there a way in vim to select two lines and diff them to show any differences between the two?
For example, given the two lines in vim:
AVeryLongReturnType* MyLongClassName:hasAnEvenLongerFunction(That *is, Overloaded *with, Multiple *different, Parameter *lists);
AVeryLongReturnType* MyLongClassName:hasAnEvenLongerFunction(That *is, Overloaded *with, Multiple *different, Parameter *1ists);
I would like vim to tell me that the two lines are in fact different because each spells "lists" differently. Is this possible, and if so, how do I do it?
A quick and dirty solution is to just select both lines and sort them while removing duplicates:
select lines
":sort u"
if only one line remains, both were equal
if both remain, there most be some difference
An undo recovers everything again.
An alternative to #sehe's approach would not require the use of temp files:
funct! DiffTwoTexts(text1, text2)
new
put =a:text1
normal ggdd
diffthis
new
put =a:text2
normal ggdd
diffthis
endfunct
funct! DiffTwoLines(line1, line2)
let text1 = getline(a:line1)
let text2 = getline(a:line2)
call DiffTwoTexts(text1, text2)
endfunct
comma! DiffWithNext call DiffTwoLines('.', line('.') + 1)
This will still be pretty hard to read, since it keeps everything on a single line, so I came up with this modification:
funct! EvalTextPreprocessor(expr, text)
let text = a:text
return eval(a:expr)
endfunct
comma! -nargs=1 DiffWithNextPre call DiffTwoTexts(
\ EvalTextPreprocessor(<q-args>, getline('.')),
\ EvalTextPreprocessor(<q-args>, getline(line('.') + 1)))
This new command takes a vimscript expression as its argument, wherein the variable text refers to whichever line is being preprocessed. So you can call, e.g.
DiffWithNextPre split(text, '[(,)]\zs')
For your sample data, this gives the two buffers
AVeryLongReturnType* MyLongClassName:hasAnEvenLongerFunction(
That *is,
Overloaded *with,
Multiple *different,
Parameter *lists)
;
and
AVeryLongReturnType* MyLongClassName:hasAnEvenLongerFunction(
That *is,
Overloaded *with,
Multiple *different,
Parameter *1ists)
;
Only the lines that start with Parameter are highlighted.
You can even build up from there, creating a command
comma! DiffTwoCFunctionSigs DiffWithNextPre split(text, '[(,)]\s*\zs')
Notice that I modified the regexp a bit so that it will keep trailing spaces at the end of lines. You could get it to ignore them entirely by moving the \s* to after the \zs. See :help /\zs if you're unfamiliar with what that vim-specific RE atom does.
A nicety would be to make the command take a range (see :help command-range), which you could use by diffing the first line of the range with the last line. So then you just visual-select from the first line to the second and call the command.
I used linediff.vim.
This plugin provides a simple command, ":Linediff", which is used to diff two separate blocks of text.
That is not a feature, however it is easily scripted, e.g. in your vimrc:
function! DiffLineWithNext()
let f1=tempname()
let f2=tempname()
exec ".write " . f1
exec ".+1write " . f2
exec "tabedit " . f1
exec "vert diffsplit " . f2
endfunction
This will open the current and next lines in vertical split in another tab.
Note that this code is a sample
it doesn't check whether next line exists (there are any following lines)
it doesn't cleanup the tempfiles created
a nice improvement would be to take a range, or use the '' mark to select the other line
You can leave off the 'vert' in order to have a horizontal split
Map it to something fancy so you don't have to :call it manually:
:nnoremap <F10> :call DiffLineWithNext()^M
you could also just create a new empty window buffer and copy line, then make command:
:windo diffthis
this should open a new window showing the differences of those 2 lines

Resources