WSL, Neovim, Lua, and the Windows Clipboard
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.
g:clipboard
global variablepbcopy
,pbpaste
(macOS)wl-copy
,wl-paste
(if$WAYLAND_DISPLAY
is set)xclip
(if$DISPLAY
is set)xsel
(if$DISPLAY
is set)lemonade
(for SSH) https://github.com/pocke/lemonadedoitclient
(for SSH) http://www.chiark.greenend.org.uk/~sgtatham/doit/win32yank
(Windows)termux
(viatermux-clipboard-set
,termux-clipboard-set
)tmux
(if$TMUX
is set)
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.