In vim, how to insert shell command output when executing contents of visual block - vim

With Vim, I often execute lines of a bash script while editing it,
using :'<,'>w !bash after selecting the lines with visual block.
Now I want the output of command to be inserted in the script.
How can I do that?

If you want your code to be replaced with its output, you can simply remove the w from your command. That leaves you with:
:'<,'>!bash`
which acts like a filter for the selected lines. See :help :!.
If you want to insert the output of your code below itself like so:
echo 1
1
you will need to be a little bit creative:
:'<,'>t'>|'[,']!bash
:[range]t[address] copies lines in [range] below line [address]. Here, we copy the visually selected lines below themselves.
| separates Ex commands.
'[,']!bash works like above: it filters the lines in the given range through bash. Here, the range covers the latest changed lines, in this case the lines we just copied. See :help '[.
Possible improvements…
Make the command less noisy with :help :silent:
:'<,'>t'>|sil'[,']!bash
Prepend a comment character:
:'<,'>t'>|sil'[,']!bash|sed 's/^/\# /'
Turn it into a visual mode mapping:
xnoremap <key> :t'>\|sil'[,']!bash\|sed 's/^/\# /'<CR>

Related

Vim advanced multiline edits

I'm trying to getting into using more advanced vim features.
How would people go about for the following edit?
from this:
ssn=token_payload.fnr,
fname=token_payload.displayName,
email=token_payload.email,
login=token_payload.username,
to this:
ssn=token_payload['fnr'],
fname=token_payload['displayName'],
email=token_payload['email'],
login=token_payload['username'],
Command line :norm command
I would apply the following normal commands to all lines in the file:
" note that in the real command, <Esc> would be a literal
" press of the escape key (see explanation below)
:%norm f.s['<Esc>f,i']
apply to the whole file: %
the following normal mode commands:norm
move to the period: f.
substitute with opening square bracket and quote: s['
escape insert mode (press ctrl+v to enter a literal character, then escape -
you'll see a gray symbol appear): ^[
move to the comma: f,
insert the quote and closing square bracket: i']
I started using the command line way instead of macros recently since I find
that you can think it over more easily (particularly if you compose the command
in the command buffer with q: - see :help command-buffer).
Use a macro
Another way is to record a macro:
qa0f.s['<Esc>f,i']<Esc>jq
Which you can then deploy on the current line with #a and repeat with ##.
Or use :%norm #a to run the macro on each line.
It's basically the same as above, but instead of :%norm you use qa to
record into the a register (you can use any letter). Then perform the edit. I
added a drop down one line with j before stopping the recording with q.
You can edit the macro after recording it by pasting the contents of the
register ("ap), edit them, and yank them back ("ay$) before replaying it.
Using an external tool
If I wanted to perform multiple substitutions with a single command, I would
filter the text through an external program like sed:
:%!sed "s/\./['/; s/,$/'],/"
One more g[ood] thing
An extremely powerful tool is the :g[lobal] command! (see :help :g) I've
been using it a lot in combination with the norm command. For example, if I
wanted to get all the paragraphs in a document formatted nicely, but not affect
indented text (which could be code blocks, or tables etc.) I would do:
:%g/^\w/norm gqap
This means, for any line with a letter at the very start of the line, apply the
command gqap which applies the normal mode command gq to 'a paragraph'.
You might also want to capitalise the first word and increase the header level
of all the markdown headings like so:
:%g/^#/norm w~I#
This would change this:
# a heading
some text.
## another heading
some more text
```sh
# and a comment in some code will be unaffected
print('hello world')
```
## a further heading
some text
# conclusion
into this:
## A heading
some text.
### Another heading
some more text
```sh
# and a comment in some code will be unaffected
print('hello world')
```
### A further heading
some text
## Conclusion
see these videos for 'advanced' vim stuff
I'd implement this as an :s command. For example, this command would make the requested changes:
:%s/\.\(.*\),/['\1'],/
That operates on all lines %, matches the dot and comma and puts everything in between into a group (\(.*\)), and then replaces it with the desired value, matching the first group (\1).
If you want to operate on a different set of lines, you can write :1,4 instead of :%, or write :'<,'> to operate on the visual selection.

Exiting exe mode in a macro

I had a large file I was trying to reformat which involved removing the 2nd to nth repeating sets on 2 to 100 lines per duplicate.
The data looked like
element1.element2.element...field.comment
I wanted to remove the repetition in elements after the first instance so of course I went complicated :) and did a macro something like
In a macro Yanked first element on current line to register p and then processed lines yanking the first element into register o and then doing, still in the macro
:if (#p=!#o)|:.s/paste register p//g|else|:norm! j|endif
Now this worked OK except when it got to a line where #p<>#o the :norm! j part stayed in : mode until I manually escaped once or twice then executed the :norm! j command.
I solved the problem an easier way but would like to know why it was only on the else portion that it wouldn't leave :ex mode.
From :help norm
:norm[al][!] {commands} *:norm* *:normal*
...
This command cannot be followed by another command,
since any '|' is considered part of the command.
...
An alternative is to use |:execute|, which uses an
expression as argument. This allows the use of
printable characters to represent special characters.
Example: >
:exe "normal \<c-w>\<c-w>"
So this would do the trick:
:if (#p=!#o)|:.s/paste register p//g|else|:exe "norm j"|endif

Vim: How to delete the same block of text over the whole file

I'm reviewing some logs with Java exception spam. The spam is getting is making it hard to see the other errors.
Is is possible in vim to select a block of text, using visual mode. Delete that block every place it occurs in the file.
If vim can't do it, I know silly question, vim can do everything. What other Unix tools might do it?
Sounds like you are looking for the :global command
:g/pattern/d
The :global command takes the form :g/{pat}/{cmd}. Read it as: run command, {cmd}, on every line matching pattern, {pat}.
You can even supply a range to the :delete (:d for short) command. examples:
:,+3d
:,/end_pattern/d
Put this togehter with the :global command and you can accomplish a bunch. e.g. :g/pat/,/end_pat/d
For more help see:
:h :g
:h :d
:h :range
Vim
To delete all matching lines:
:g/regex/d
To only delete the matches themselves:
:%s/regex//g
In either case, you can copy the visual selection to the command line by yanking it and then inserting it with <C-r>". For example, if your cursor (|) is positioned as follows:
hello wo|rld
Then you can select world with viw, yank the selection with y, and then :g/<C-r>"/d.
sed
To delete all matching lines:
$ sed '/regex/d' file
To only delete the matches themselves:
$ sed 's/regex//g' file
grep
To delete all matching lines:
$ grep -v 'regex' file
grep only operates line-wise, so it's not possible to only delete matches within lines.
you can try this in vim
:g/yourText/ d
Based on our discussion in the comments, I guess a "block" means several complete lines. If the first and last lines are distinctive, then the method you gave in the comments should work. (By "distinctive" I mean that there is no danger that these lines occur anywhere else in your log file.)
For simplifications, I would use "ay$ to yank the first line into register a and "by$ to yank the last line into register b instead of using Visual mode. (I was going to suggest "ayy and "byy, but that wold capture the newlines)
To be on the safe side, I would anchor the patterns: /^{text}$/ just in case the log file contains a line like "Note that {text} marks the start of the Java exception." On the command line, I would use <C-R>a and <C-R>b to paste in the contents of the two registers, as you suggested.
:g/^<C-R>a$/,/^<C-R>b$/d
What if the yanked text includes characters with special meaning for search patterns? To be on the really safe side, I would use the \V (very non-magic) modifier and escape any slashes and backslashes:
:g/\V\^<C-R>=escape(#a, '/\')<CR>\$/,/\V\^<C-R>=escape(#b, '/\')<CR>\$/d
Note that <C-R>= puts you on a fresh command line, and you return to the main one with <CR>.
It is too bad that \V was not available when matchit was written. It has to deal with text from the buffer in a search pattern, much like this.

Reusing the previous range in ex commands in 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.

Execute shell command without filtering from Vim

I want to select a block of text (for example, V%) and use the text as input to a shell command (for example, wc or pbcopy) - but I don't want to alter the current buffer - I just want to see the output of the command (if any) then continue editing without any changes.
Typing V%!wc translates to :'<,'>!wc and switches the block of text for the output of the wc command.
How do you pipe a chunk of text to an arbitrary shell command without affecting the current buffer?
Select your block of text, then type these keys :w !sh
The whole thing should look like:
:'<,'>w !sh
That's it. Only took me 8 years to learn that one : )
note: typing : after selecting text produces :'<,'> a range indicating selection start and end.
Update 2016: This is really just one use of the generic:
'<,'>w !cli_command
Which basically lets you "send" arbitrary parts of your file to external commands and see the results in a temporary vi window without altering your buffer. Other useful examples would be:
'<,'>w !wc
'<,'>w !to_file my_file
I honestly find it more useful to alter the current buffer. This variety is simply:
'<,'>!wc
'<,'>!to_file my_file
One possibility would be to use system() in a custom command, something like this:
command! -range -nargs=1 SendToCommand <line1>,<line2>call SendToCommand(<q-args>)
function! SendToCommand(UserCommand) range
" Get a list of lines containing the selected range
let SelectedLines = getline(a:firstline,a:lastline)
" Convert to a single string suitable for passing to the command
let ScriptInput = join(SelectedLines, "\n") . "\n"
" Run the command
let result = system(a:UserCommand, ScriptInput)
" Echo the result (could just do "echo system(....)")
echo result
endfunction
Call this with (e.g.):
:'<,'>SendToCommand wc -w
Note that if you press V%:, the :'<,'> will be entered for you.
:help command
:help command-range
:help command-nargs
:help q-args
:help function
:help system()
:help function-range
Update: my answer is nonsense.
#pixelearth's answer is good, but I had a little trouble understanding what he did exactly, so I wrote the following. This sequence of commands let's you execute wc -l on your visual selection. wc -l simply counts the number of lines passed to it.
In Vim go into Visual Mode using v
Select a few lines by going down: jjjj
Type : which Vim will translate to :'<,'>
Type w !wc -l, your complete commandline should now be :'<,'>w !wc -l
Press Enter to get the result of your command (in this example it would be 4)
Press Enter to continue editing
I don't understand what exactly happens at step 3 and 4 but I do know that it works.
I know it's not the ideal solution, but if all else fails, you could always just press u after running the command to undo the buffer change.

Resources