The kitty terminal emulator has great support for copy and paste using cmd+c
and cmd+v
on macOS. Neovim has a very robust clipboard system using y
and p
. Neovim users can configure y
and p
to integrate directly into the system clipboard. However, I wanted to keep neovim’s default clipboard register separate from the system clipboard. I wanted to use y
and p
inside vim only, then use cmd+c
and cmd+v
to copy and paste between neovim and the rest of the system. This turned out to be surprisingly complicated.
First, I separated the neovim and system clipboard by commenting out the following line in my ~/.config/nvim/init.lua
file.
-- Don't use the system clipboard by default -- vim.opt.clipboard = "unnamedplus"
Neovim and kitty have separate systems for selecting text. Therefore, Kitty doesn’t know which text you are trying to copy.
I was excited to learn that I could add cmd
mappings in neovim. I added the following keymaps to tell neovim to copy/paste to the system clipboard using cmd+c
and cmd+v
.
vim.keymap.set({ 'n', 'v' }, '<D-c>', '"+y') -- cmd+c to copy to system clipboard vim.keymap.set({ 'n', 'v' }, '<D-v>', '"+p') -- cmd+v to paste from system clipboard
However, these keymaps didn’t work at first. The shortcuts were being handled by kitty and never reached neovim at all. I was stuck on this problem for quite a while. I found the solution by looking at the vim-kitty-navigator plugin.
The fix is to map cmd+c
and cmd+v
to a custom kitten. A kitten is a kitty extension written in python. The code forwards the keys to neovim if it is running. Otherwise, it does the normal kitty clipboard operations. I lifted the code below from vim-kitty-navigator and modified it for my purposes.
I wrote the following program to ~/.config/kitty/pass_keys.py
:
import re from kittens.tui.handler import result_handler from kitty.key_encoding import KeyEvent, parse_shortcut def is_window_vim(window, vim_id): fp = window.child.foreground_processes return any(re.search(vim_id, p['cmdline'][0] if len(p['cmdline']) else '', re.I) for p in fp) def encode_key_mapping(window, key_mapping): mods, key = parse_shortcut(key_mapping) event = KeyEvent( mods=mods, key=key, shift=bool(mods & 1), alt=bool(mods & 2), ctrl=bool(mods & 4), super=bool(mods & 8), hyper=bool(mods & 16), meta=bool(mods & 32), ).as_window_system_event() return window.encoded_key(event) def main(): pass @result_handler(no_ui=True) def handle_result(args, result, target_window_id, boss): cmd = args[1] # bottom, top, left, right, copy, paste key_mapping = args[2] # ctrl+j, ctrl+k, ctrl+h, ctrl+l, ctrl+c, ctrl+v vim_id = args[3] if len(args) > 3 else "n?vim" window = boss.window_id_map.get(target_window_id) if window is None: return if is_window_vim(window, vim_id): for keymap in key_mapping.split(">"): encoded = encode_key_mapping(window, keymap) window.write_to_child(encoded) elif cmd == "copy": # this is new window.copy_and_clear_or_interrupt() elif cmd == "paste": # this is new window.paste_selection_or_clipboard() else: boss.active_tab.neighboring_window(cmd)
Lastly, I added the following like to my kitty config in ~/.config/kitty/kitty.conf
map cmd+c kitten pass_keys.py copy cmd+c map cmd+b kitten pass_keys.py paste cmd+v
Now, when I hit cmd+c
or cmd+v
, kitty will detect if neovim is running. If neovim is in the foreground, it will forward the keypress so that nvim’s keymaps work using nvim’s selected text. Otherwise, kitty will copy and paste using it’s own methods of copy/paste and selection.
You can find my neovim config file on github and my kitty config file in this gist
With all of this in place, I can use cmd+c
and cmd+v
to copy/paste to the system clipboard in both the kitty terminal and in neovim. Was it worth it? Probably not. Was it fun? Sure.
2024-04-17