In Vim, run command and redirect stdout to specific buffer - vim

Say I open two buffers side by side and enter the source code into buffer 1. I want to run the compiler (or any command line program) and see its output (stdout) in buffer 2.
How do I feed the current or specific buffer as stdin to this command line program? If this it not possible, I can save source code to the file and specfy it as parameter to compiler; but anyway I want to see output in buffer 2.

If you look at :h :b:
:[N]b[uffer][!] [+cmd] [N] :b :bu :buf :buffer E86
Edit buffer [N] from the buffer list. If [N] is not given,
the current buffer remains being edited. See :buffer-! for
[!]. This will also edit a buffer that is not in the buffer
list, without setting the 'buflisted' flag.
Also see +cmd.
And for +cmd:
+cmd [+cmd]
The [+cmd] argument can be used to position the cursor in the newly opened
file, or execute any other command:
+ Start at the last line.
+{num} Start at line {num}.
+/{pat} Start at first line containing {pat}.
+{command} Execute {command} after opening the new file.
{command} is any Ex command.
So:
:2b +r!date
Would open buffer 2, and read in the output of the date command.

You can use :r! command to execute shell command and read its output to current buffer.

Related

VI read line not giving desired output

I have a file called test, I open it using vi as such:
vi test
Now I want to insert a line through a shell command, for simplicity I use a printf:
:r! printf %s hello
However the line that is entered is
tests
i.e. the name of the file with a s appended.
If I enter the same command in terminal directly, it works fine.
What I want to do is ultimately be able to encode a string in base64 and enter it on the same line as where my cursor is in vi, so that I won't have to copy the string in a separate terminal, encode it, and copy it back into vi. How can I do this? What am I doing wrong?
The first stage of processing a command line in vim is expanding it. % is expanded to the name of the current file — test in your case. %s is expanded to tests.
To avoid expanding protect the special character with a backslash:
:r! printf \%s hello

How to fzf recent files of vim/nvim, not inside vim but from terminal

I know how to fzf.vim, but I'd like to open from terminal.
Grepping history or viminfo may be achieve thst, but I wonder if there is any smart way.
This is how you can save the list of recent files from vim to a file:
vim -c "call append(0, v:oldfiles)" -c "write vim-oldfiles.tmp" -c exit
Put v:oldfiles (the list of recent files saved in ~/.viminfo) into the first (new and empty at the start) buffer, write the buffer to a file, exit.
Now you can pass the content of file to fzf.
Not exact solution. But you could open a terminal buffer on the lower part of your vim edit like an IDE and use your terminal fzf
However, not sure if this will let you open a file in a new vim tab
I have an zsh autoloaded function called old:
function old(){
vim -c 'redir >> /tmp/oldfiles.txt | silent oldfiles | redir end | q'
sed -i '/NvimTree$/d' /tmp/oldfiles.txt
local fname
fname=$(awk '/home/ && !/man:/ {print $2}' /tmp/oldfiles.txt | fzf) || return
vim "$fname"
\rm /tmp/oldfiles.txt
}
If you're having trouble executing vim on files that have ~ in their path (vim open a new blank file instead of the desired file) because fzf and vim don't expand tilde (~), here's how I do it:
export FZF_DEFAULT_OPTS=$FZF_DEFAULT_OPTS"
--bind 'ctrl-e:execute(vim -c \"execute \\\"edit\\\" expand({})\" >/dev/tty)'
"
It's trial and error, based on this.
Combining some of the other answers, here's a version that does not need a temporary file and writes to stdout (so you can pipe this into another command, or capture the output using $(...)).
vim -e -c "redir >> /dev/fd/100 | for f in v:oldfiles | silent echo substitute(f, \"^\\\\~\", \$HOME, \"g\") | endfor | redir end | q" 100>&1 &>/dev/null
This solution combines elements from other solutions, but with some improvements:
It uses some shell redirection to duplicate stdout to some free fd (100>&1) and then uses /dev/fd/100 to force writing output there. This ensures that vim actually writes to stdout rather than the terminal. Note that this can also be made to work using /dev/fd/1 (but only when omitting redir end for some reason), but then we cannot apply the next point.
It redirects stdout (and for good measure) also stderr to /dev/null, to prevent vim writing some terminal escape codes to stdout on startup, so using a different fd ensures clean output.
It uses vim in "ex" mode (vim -e) to suppress the "Vim: Warning: Output is not to a terminal" output and accompanying delay. [source]
It uses a for-loop to iterate over v:oldfiles to output just the filenames (the oldfiles command used by https://stackoverflow.com/a/70749181/740048 adds line numbers).
It uses a substitute to expand ~ in the filenames returned by vim (making the returned filenames easier to proces. Normally, shells like bash expand ~ in arguments passed to commands, but this happens only for tildes in the command typed, not tildes that result from variables or command substitution. To prevent having to rely on unsafe eval'ing later, better to expand (just) the tildes beforehand.
I also tried using the append / write combo from https://stackoverflow.com/a/60018642/740048, which worked with the /dev/fd/100 trick, but then ended up putting /dev/fd/100 in the list of oldfiles, so I did not use that approach.

Capture the output of an interactive script in vim

I have a an interactive Perl script, which prints prompts to STDERR and reads lines from STDIN. The final output of this script is an IP address, printed to STDOUT. Here's a numpty version of such a script as an example.
my #pieces;
for (1..4) {
print STDERR "please enter piece $_ of the IP:"; chomp(my $in = <>);
push #pieces, $in;
}
print join '.', #pieces;
print "\n";
I use the vim-fireplace vim plugin. This plugin has a feature where I can say:
:Connect nrepl://127.0.0.1:9999
I want to know how to configure vim so that when I issue a particular command, let's say:
:InteractiveConnect
it will do the following:
Run the Perl script, allowing me to enter 4 pieces of the IP address.
Capture the IP address output by the Perl script.
Interpolate the IP address into the :Connect command
Run the :Connect command.
A bit more info based on some of the responses:
If I call this script using:
:!/path/to/myscript.pl
Then it executes fine and I am able to see the result from it printed in the vim window, followed by
Press ENTER or type command to continue
If the output of the script is being saved in some buffer after execution via !, is it possible to get access to that buffer in vimscript and just capture the bit I want (the last line) with a regex?
Okay, there's probably a more elegant way to do this, but how about this:
function! <SID>InteractiveConnect()
let tempfile=tempname()
exe '!/path/to/your/script.pl >' . shellescape(tempfile)
try
exe 'Connect nrepl://' . readfile(tempfile, '', -1)[0]
finally
call delete(tempfile)
endtry
endfunction
command! -nargs=0 InteractiveConnect call <SID>InteractiveConnect()
This creates a temporary file, writes to it with the script (using system() doesn't work because it doesn't wait for input), reads the last line in the tempfile to the Connect command, and then finally deletes the tempfile.
Maybe something like:
exec 'Connect nrepl://' . matchstr(system('your/script.pl'), '^.\+\%$')
(Untested.) This runs the script using system() then matches the output against the regular expression ^.\+\%$, (where \%$ means end-of-file; if your file is terminated with a newline, an additional \n might be neccessary before it) and feeds the matched str to the Connect command. .

How to replace the contents of the current buffer with the contents of a file?

I have an external script that takes a Javascript file and automatically fixes some style issues, I want to apply it to the current buffer right before writing (BufWritePre,FileWritePre).
So my idea is to:
w /tmp/foo Write the current buffer contents to a temporary file
silent !fixStyle /tmp/foo Run the script on that file.
Replace the contents of the current buffer with the contents of /tmp/foo
The thing is that I don't know how to do the third step.
One way is deleting current contents (1,$d, i.e., delete between line 1 and last line), and read in the target file starting from line 0 (before line 1 so that there is no blank line):
:1,$d|0r ~/.hck/input
Another way is using a filter (cat in this case) to replace all of the content (%) with the output of the filter:
:%!cat /tmp/foo
You could use set autoread. It detects when the file has been changed on disk and reloads the current buffer from the file. So you could save the file, run the script and vim will reload the buffer with the change contents.
Autoread's help is copied below
*'autoread'* *'ar'* *'noautoread'* *'noar'*
'autoread' 'ar' boolean (default off)
global or local to buffer |global-local|
{not in Vi}
When a file has been detected to have been changed outside of Vim and
it has not been changed inside of Vim, automatically read it again.
When the file has been deleted this is not done. |timestamp|
If this option has a local value, use this command to switch back to
using the global value: >
:set autoread<

Why the -b(binary) option doesn't work in vim?

As vim doc said, I can use the -b option to open a binary file.
-b Binary mode. File I/O will only recognize <NL> to separate
lines. The 'expandtab' option will be reset. The 'textwidth'
option is set to 0. 'modeline' is reset. The 'binary' option
is set. This is done after reading the vimrc/exrc files but
before reading any file in the arglist. See also
|edit-binary|. {not in Vi}
I use this command to open vim:
$ vim --cmd 'set et' -u NONE -b
I type this command to view options:
:set et? bin?
expandtab
binary
The et(expandtab) option wasn't reset. Why?
Thanks for your help!
Well the issue is, simply, that --cmd -c or +cmd arguments are executed after processing the other flags. This makes sense, as it would not effectively do anything otherwise.
:verbose set et?
would tell you exactly that. In case you need a workaround for your particular sample vim +'set binary' (unlikely since et != binary)
You are right on the docs for --cmd. So it comes down to the order in which command line flags are interpreted, which is basically 'undefined'. Although
This is done after reading the vimrc/exrc files but before reading any file in the arglist
could be taken to imply 'before processing other command line arguments'.
Note The '+' commands essentially go with specific files and are (AFAICT) processed in the order in which they appear, even when intermixed with filename arguments.

Resources