I'm trying to create a Powershell function that acts as a wrapper for Vim in WSL. I would like this wrapper to be able to accept input from the pipeline.
In WSL, it's possible to do something like:
$ cat manylines.txt | vim -
The -
indicates that Vim should read the input from stdin, which is then redirected through the pipeline. This will open vim with the command output already in the buffer, without any file associated.
(You could also simply open the file with vim manylines.txt
, but that wouldn't work in general for the outputs of other commands or executables).
The same is possible in Powershell, with Windows native Vim:
Get-Content manylines.txt | vim -
It's possible to go one step further and execute Vim within another instance of Powershell or cmd:
Get-Content manylines.txt | powershell -Command vim -
Get-Content manylines.txt | cmd /c vim -
However, the same does not work with WSL Vim.
# doesn't work, Vim freezes
Get-Content manylines.txt | wsl vim -
Get-Content manylines.txt | wsl bash -c "vim -"
# doesn't work, Vim quits reporting "Error reading Input"
Get-Content manylines.txt | wsl bash -c vim -
In the first two cases, Vim opens correctly with the contents of manylines.txt in the buffer... and then refuses to accept any keyboard input. It is not possible to navigate, edit, or even quit with :q!
.
Using a temporary powershell variable seems to fix things, but only when the input consists of a single line
# "works as long as the file only has a single line of text"
# "$tmpvar has type System.String (not an array)"
$tmpvar = Get-Content singleline.txt
wsl bash -c "echo $tmp | vim -"
But this breaks when $tmpvar becomes a String[] array or a String with any newlines
# sort of works, but newlines are converted to a single space
$tmpvar = [string[]] Get-Content manylines.txt
wsl bash -c "echo $tmp | vim -"
# combines the strings in the array, inserts a newline between each pair of elements
$tmpvar = ([string[]] Get-Content manylines.txt) -join "`n"
# doesn't work, bash interprets the newline as the start of the next command
wsl bash -c "echo $tmp | vim -"
How do I get WSL Vim to accept pipeline input from Powershell?
Other Notes:
- I could use a temporary file and pass the file to WSL Vim, but that's not an elegant solution and leaves a file that has to be cleaned up. Unless the temporary file is in the current directory, it also involves extra shenanigans with wslpath
CodePudding user response:
Indeed it seems that piping input to vim
via wsl
seems to break vim
's keyboard interface with respect to cursor movements (I was still able to type :q!
to quit, for instance).
The workaround is to refine the approach you've discovered, namely to provide the input as part of a shell command passed to wsl
instead of using the pipeline:
Doing so requires careful escaping and transformations, as shown in the following wrapper function:
# PowerShell wrapper function for invoking vim via WSL
function vim {
if ($MyInvocation.ExpectingInput) {
$allInput = ($input | Out-String) -replace '\r?\n', "`n" -replace "'", "'\''" -replace '"', '\"'
wsl -e sh -c "printf %s '$allInput' | vim $args -"
}
else {
wsl -e vim $args
}
}
The only caveat is that you may run into the max. command-line length.
With this function defined, you can call, say (
, as a sample option, tells vim
to place the cursor at the end of the input):
vim somefile.txt
or
Get-ChildItem | vim