tl;dr

init.lua

in_wsl = os.getenv('WSL_DISTRO_NAME') ~= nil

if in_wsl then
    vim.g.clipboard = {
        name = 'wsl clipboard',
        copy =  { ["+"] = { "clip.exe" },   ["*"] = { "clip.exe" } },
        paste = { ["+"] = { "nvim_paste" }, ["*"] = { "nvim_paste" } },
        cache_enabled = true
    }
end

nvim_paste shell script:

#!/bin/sh

# Check for win32yank.exe executable
if command -v win32yank.exe >/dev/null 2>/dev/null; then
    # The --lf option pastes data unix style. Which is what I almost always want.
    win32yank.exe -o --lf
else
    # Else rely on PowerShell being installed and available.
    powershell.exe Get-Clipboard | tr -d '\r' | sed -z '$ s/\n$//'
fi

The Windows Subsystem for Linux (WSL) has been great, however, there are often WSL specific tweaks that need to be made to make critical functionality possible.

One of those critical functions is the clipboard interaction.

In Neovim, a clipboard provider handles this interaction (see :h clipboard). There is a well-defined order of preference for finding this provider.

Things were working fine until something changed, and the $DISPLAY variable started getting set by some program. Following the order of preference above, Neovim attempted to use xclip as the clipboard provider. As there is no X-server out of the box in the WSL, xclip doesn’t work.

So the solution is to explicitly define what I want in the global variable g:clipboard.

There are several examples of setting this variable in the Vim script environment, but I didn’t come across much for the Lua environment for Neovim.

The first task is defining what external commands I want to use for interacting with the Windows clipboard from the WSL.

Between the copying or pasting operation, the choice for copying is simpler. Windows comes with the command line executable clip.exe which takes standard input and puts it in the clipboard. It works great. However, there is no counterpart executable for pasting data from the clipboard to standard output.

In the list of preferences above, there is win32yank listed for Windows. win32yank is a quite simple copy/paste utility written in Rust (GitHub). Of course, since it’s written in Rust, it is blazingly fast. So if this executable is available, I’d prefer to use that.

However, my setup can change such that win32yank may not be available. The always available backup choice in Windows is PowerShell.

The cmdlet to paste from the Windows clipboard to standard output in PowerShell is Get-Clipboard:

powershell.exe Get-Clipboard

The only issue is that it will add an extra newline that is not desired.

The other tweak to these pasting commands is that I’m almost always wanting to paste into documents that have Unix newlines.

For win32yank you can specify that the pasted data comes out with Unix newlines using the --lf option. With the PowerShell command, I just pipe the data through tr -d '\r' to remove all the carriage returns.

With our desired pasting programs and logic determined, I put it together in a simple shell script that looks like this:

#!/bin/sh

# Check for win32yank.exe executable
if command -v win32yank.exe >/dev/null 2>/dev/null; then
    # The --lf option pastes data unix style. Which is what I almost always want.
    win32yank.exe -o --lf
else
    # Else rely on PowerShell being installed and available.
    powershell.exe Get-Clipboard | tr -d '\r' | sed -z '$ s/\n$//'
fi

I call this script nvim_paste, and it lives in my dotfiles so that it’s available on my $PATH.

So in summary, we’ll use clip.exe for copying and nvim_paste for pasting.

g:clipboard and init.lua

The final step is to correctly set the g:clipboard variable to use these commands. I use Lua for my Neovim configuration. So again, there is a bit of translation to get this to work.

The first thing to do is check whether I’m in a WSL environment. None of this matters unless that is true. On my home Linux machines, I want to use xclip.

To determine whether I’m in a WSL environment, I check for the presence of the environment variable WSL_DISTRO_NAME.

in_wsl = os.getenv('WSL_DISTRO_NAME') ~= nil

The g:clipboard variable is Vim script dictionary. In Lua, the analogous data type is the table. The keys to set are 'name', 'copy', 'paste', and 'cache_enabled'. For 'copy' and 'paste', the values are dictionaries themselves, with keys '+' and '*'. For Windows, we don’t need to worry about the difference in the '+' and '*'.

We can access the global vim variables through the vim.g dictionary.

Putting it all together, the final Lua code that is in my init.lua is:

in_wsl = os.getenv('WSL_DISTRO_NAME') ~= nil

if in_wsl then
    vim.g.clipboard = {
        name = 'wsl clipboard',
        copy =  { ["+"] = { "clip.exe" },   ["*"] = { "clip.exe" } },
        paste = { ["+"] = { "nvim_paste" }, ["*"] = { "nvim_paste" } },
        cache_enabled = true
    }
end

You can find my entire init.lua on GitHub.