The ^@
you're referring to is actually a newline character, so the problem you describe will happen when your visual selection includes the newline (e.g. v$
to select until the end of the line) or when it spawns multiple lines.
Assuming your external tool you're shelling out to is able to handle multiline strings appropriately, Vim's shellescape()
will be able to handle it in a way that the shell will interpret it correctly, by turning it into a single quoted string that can embed newlines.
So multiline\n
becomes 'multiline\n'
through shellescape()
(where the \n
here represents an actual newline character), and the shell is fine with it.
The problem you're having is that :execute
is now seeing two separate commands, in this case:
$read !bitwise --no-color 'multiline
And:
'
Or, more geneally, for first line\nsecond line
:
$read !bitwise --no-color 'first line
second line'
There are two possible solutions to this problem. The first is to pass a non-zero second argument to shellescape()
, which will make it do some extra magic to handle cases like this one with the newline. From :help shellescape()
:
With a non-zero-arg {special}
the <NL>
character is also escaped. [...]
Example of use with a :!
command:
:exe '!dir ' . shellescape(expand('<cfile>'), 1)
The example itself is using : execute
, so it's actually a good illustration of your issue.
So one possible solution is:
silent execute '$read !'. "bitwise --no-color ". shellescape(@@, 1)
Another, actually better solution, is to use the system()
function to shell out instead, in which case you don't need to worry about escaping the string from :execute
.
In order to put the contents of the system()
result into the new buffer, you can use the :put
command together with the @=
expression register.
Putting it all together:
silent put =system('bitwise --no-color '.shellescape(@@))
You can also use setline()
instead of :put
, which makes it even easier to get rid of the empty line at the start of the buffer. With setline()
, use systemlist()
, to get the shell output as a list of lines:
call setline(1, systemlist('bitwise --no-color '.shellescape(@@)))