├── .gitignore ├── neighboring_window.py ├── kitty-vim-tmux-navigator.tmux ├── License.md ├── doc └── kitty-navigator.txt ├── plugin └── kitty_navigator.vim ├── pass_keys.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | doc/tags 2 | -------------------------------------------------------------------------------- /neighboring_window.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | pass 3 | 4 | 5 | def handle_result(args, result, target_window_id, boss): 6 | boss.active_tab.neighboring_window(args[1]) 7 | 8 | 9 | handle_result.no_ui = True 10 | -------------------------------------------------------------------------------- /kitty-vim-tmux-navigator.tmux: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Kitty Tmux navigator 4 | # SSH aware kitty change window 5 | tmux if-shell '[ $SSH_TTY ]' 'to="--to=tcp:localhost:$KITTY_PORT "' 'to=""' 6 | move='kitty @ ${to}kitten neighboring_window.py' 7 | 8 | # Key Binds 9 | tmux bind-key -n 'C-h' if-shell "[ #{pane_at_left} != 1 ]" "select-pane -L" "run-shell '$move left'" 10 | tmux bind-key -n 'C-j' if-shell "[ #{pane_at_bottom} != 1 ]" "select-pane -D" "run-shell '$move bottom'" 11 | tmux bind-key -n 'C-k' if-shell "[ #{pane_at_top} != 1 ]" "select-pane -U" "run-shell '$move top'" 12 | tmux bind-key -n 'C-l' if-shell "[ #{pane_at_right} != 1 ]" "select-pane -R" "run-shell '$move right'" 13 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Matthew Steedman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /doc/kitty-navigator.txt: -------------------------------------------------------------------------------- 1 | *kitty-navigator.txt* Plugin to allow seamless navigation between kitty and vim 2 | 3 | ============================================================================== 4 | CONTENTS *kitty-navigator-contents* 5 | 6 | 7 | ============================================================================== 8 | INTRODUCTION *kitty-navigator* 9 | 10 | Vim-kitty-navigator is a little plugin which enables seamless navigation 11 | between kitty panes and vim splits. This plugin is a repackaging of Mislav 12 | Marohinc's tmux=navigator configuration. When combined with a set of kitty key 13 | bindings, the plugin will allow you to navigate seamlessly between vim and 14 | kitty splits using a consistent set of hotkeys. 15 | 16 | NOTE: This requires kitty v0.13.1 or higher. 17 | 18 | ============================================================================== 19 | CONFIGURATION *kitty-navigator-configuration* 20 | 21 | * Custom Key Bindings 22 | let g:kitty_navigator_no_mappings = 1 23 | 24 | nnoremap {Left-mapping} :KittyNavigateLeft 25 | nnoremap {Down-Mapping} :KittyNavigateDown 26 | nnoremap {Up-Mapping} :KittyNavigateUp 27 | nnoremap {Right-Mapping} :KittyNavigateRight 28 | nnoremap {Previous-Mapping} :KittyNavigatePrevious 29 | 30 | vim:tw=78:ts=8:ft=help:norl: 31 | -------------------------------------------------------------------------------- /plugin/kitty_navigator.vim: -------------------------------------------------------------------------------- 1 | " Maps to switch vim splits in the given direction. If there are 2 | " no more windows in that direction, forwards the operation to kitty. 3 | 4 | if exists("g:loaded_kitty_navigator") || &cp || v:version < 700 5 | finish 6 | endif 7 | let g:loaded_kitty_navigator = 1 8 | 9 | function! s:VimNavigate(direction) 10 | try 11 | execute 'wincmd ' . a:direction 12 | catch 13 | echohl ErrorMsg | echo 'E11: Invalid in command-line window; executes, CTRL-C quits: wincmd k' | echohl None 14 | endtry 15 | endfunction 16 | 17 | if !get(g:, 'kitty_navigator_no_mappings', 0) 18 | nnoremap :KittyNavigateLeft 19 | nnoremap :KittyNavigateDown 20 | nnoremap :KittyNavigateUp 21 | nnoremap :KittyNavigateRight 22 | endif 23 | 24 | command! KittyNavigateLeft call s:KittyAwareNavigate('h') 25 | command! KittyNavigateDown call s:KittyAwareNavigate('j') 26 | command! KittyNavigateUp call s:KittyAwareNavigate('k') 27 | command! KittyNavigateRight call s:KittyAwareNavigate('l') 28 | 29 | function! s:KittyCommand(args) 30 | let cmd = 'kitty @ ' . a:args 31 | return system(cmd) 32 | endfunction 33 | 34 | let s:kitty_is_last_pane = 0 35 | 36 | augroup kitty_navigator 37 | au! 38 | autocmd WinEnter * let s:kitty_is_last_pane = 0 39 | augroup END 40 | 41 | function! s:KittyAwareNavigate(direction) 42 | let nr = winnr() 43 | let kitty_last_pane = (a:direction == 'p' && s:kitty_is_last_pane) 44 | if !kitty_last_pane 45 | call s:VimNavigate(a:direction) 46 | endif 47 | let at_tab_page_edge = (nr == winnr()) 48 | 49 | if kitty_last_pane || at_tab_page_edge 50 | let mappings = { 51 | \ "h": "left", 52 | \ "j": "bottom", 53 | \ "k": "top", 54 | \ "l": "right" 55 | \ } 56 | let args = 'kitten neighboring_window.py' . ' ' . mappings[a:direction] 57 | silent call s:KittyCommand(args) 58 | let s:kitty_is_last_pane = 1 59 | else 60 | let s:kitty_is_last_pane = 0 61 | endif 62 | endfunction 63 | -------------------------------------------------------------------------------- /pass_keys.py: -------------------------------------------------------------------------------- 1 | import kitty.conf.utils as ku 2 | import kitty.key_encoding as ke 3 | from kitty import keys 4 | import re 5 | 6 | 7 | def main(): 8 | """ needed but not used """ 9 | pass 10 | 11 | def actions(extended): 12 | yield keys.defines.GLFW_PRESS 13 | if extended: 14 | yield keys.defines.GLFW_RELEASE 15 | 16 | def convert_mods(mods): 17 | """ 18 | converts key_encoding.py style mods to glfw style mods as required by key_to_bytes 19 | """ 20 | glfw_mods = 0 21 | if mods & ke.SHIFT: 22 | glfw_mods |= keys.defines.GLFW_MOD_SHIFT 23 | if mods & ke.ALT: 24 | glfw_mods |= keys.defines.GLFW_MOD_ALT 25 | if mods & ke.CTRL: 26 | glfw_mods |= keys.defines.GLFW_MOD_CONTROL 27 | if mods & ke.SUPER: 28 | glfw_mods |= keys.defines.GLFW_MOD_SUPER 29 | return glfw_mods 30 | 31 | def pass_key(key_combination: str, w): 32 | """ 33 | pass key_combination to the kitty window w. 34 | Args: 35 | key_combination (str): keypress to pass. e.g. ctrl-j 36 | w (kitty window): window to pass the keys 37 | """ 38 | mods, key, is_text = ku.parse_kittens_shortcut(key_combination) 39 | extended = w.screen.extended_keyboard 40 | for action in actions(extended): 41 | sequence = ( 42 | ('\x1b_{}\x1b\\' if extended else '{}') 43 | .format( 44 | keys.key_to_bytes( 45 | getattr(keys.defines, 'GLFW_KEY_{}'.format(key.upper())), 46 | w.screen.cursor_key_mode, extended, convert_mods(mods), action) 47 | .decode('ascii'))) 48 | print(repr(sequence)) 49 | w.write_to_child(sequence) 50 | 51 | 52 | def handle_result(args, result, target_window_id, boss): 53 | """ Main entry point for the kitten. Decide wether to change window or pass 54 | the keypress 55 | Args: 56 | args (list): Extra arguments passed when calling this kitten 57 | [0] (str): kitten name 58 | [1] (str): direction to move 59 | [2] (str): key to pass 60 | The rest of the arguments comes from kitty 61 | """ 62 | # get active window and tab from target_window_id 63 | w = boss.window_id_map.get(target_window_id) 64 | if w is None: 65 | return 66 | 67 | # Check if keyword in the foreground process 68 | proc = w.child.foreground_processes[0]['cmdline'] 69 | keywords = ['vim', 'nvim', 'ssh', 'tmux'] 70 | for keyword in keywords: 71 | if keyword in proc: 72 | pass_key(args[2], w) 73 | return 74 | 75 | # keywords not found, move to neighboring window instead 76 | boss.active_tab.neighboring_window(args[1]) 77 | 78 | handle_result.no_ui = True 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Kitty Vim Tmux Navigator 2 | ================== 3 | 4 | This plugin is a fork from [vim-kitty-navigator](https://github.com/knubie/vim-kitty-navigator) extending it's capabilities to also work with tmux pane navigation. The aim is to make navigation between Kitty windows, tmux panes, and vim splits seamless. With some extra configuration, kitty-tmux navigation works even through SSH! 5 | 6 | **NOTE**: 7 | - This requires kitty v0.13.1 or higher. 8 | - Requires newer version of tmux with 'pane_at_*' format feature. (Not sure which version, but tested tmux 2.1 and didn't work) 9 | - Works only 2 nested layer deep. So navigation for vim splits inside tmux inside kitty is not supported. 10 | 11 | Usage 12 | ----- 13 | 14 | This plugin provides the following mappings which allow you to move between 15 | Vim splits, tmux panes, and kitty window seamlessly. 16 | 17 | - `` => Left 18 | - `` => Down 19 | - `` => Up 20 | - `` => Right 21 | 22 | Installation 23 | ------------ 24 | 25 | ### VIM 26 | 27 | Use your favorite plugin manager and add the following repo to your plugin list 28 | ```vim 29 | 'NikoKS/kitty-vim-tmux-navigator' 30 | ``` 31 | And then run the plugin installation function 32 | 33 | ### KITTY 34 | 35 | To configure kitty, do the following steps: 36 | 37 | 1. Copy both `pass_keys.py` and `neighboring_window.py` kittens to the `~/.config/kitty/`. 38 | 39 | 2. Add the following to your `~/.config/kitty/kitty.conf` file: 40 | 41 | ```sh 42 | map ctrl+j kitten pass_keys.py bottom ctrl+j 43 | map ctrl+k kitten pass_keys.py top ctrl+k 44 | map ctrl+h kitten pass_keys.py left ctrl+h 45 | map ctrl+l kitten pass_keys.py right ctrl+l 46 | ``` 47 | 48 | `kitty-vim-tmux-navigator` changes `vim-kitty-navigator` vim detection method from using title name to running foreground process name. So it removes the capability to change title regex. 49 | 50 | 3. Enable kitty `allow_remote_control` and `listen_on` option: 51 | 52 | Set it on the `~/.config/kitty/kitty.conf` file: 53 | 54 | ```conf 55 | allow_remote_control yes 56 | listen_on unix:/tmp/mykitty 57 | ``` 58 | 59 | **OR** 60 | 61 | Start kitty with the `listen_on` option so that vim can send commands to it. 62 | 63 | ``` 64 | kitty -o allow_remote_control=yes --listen-on unix:/tmp/mykitty 65 | ``` 66 | 67 | The listening address can be customized in your vimrc by setting `g:kitty_navigator_listening_on_address`. It defaults to `unix:/tmp/mykitty`. 68 | 69 | ### TMUX 70 | 71 | If you're using [TPM](https://github.com/tmux-plugins/tpm), just add this snippet to your tmux.conf: 72 | 73 | ```conf 74 | set -g @plugin 'NikoKS/kitty-vim-tmux-navigator' 75 | ``` 76 | 77 | And update your plugin 78 | 79 | **Otherwise** 80 | 81 | Add the following snippet to your tmux.conf: 82 | 83 | ```sh 84 | # SSH aware kitty change window 85 | if-shell '[ $SSH_TTY ]' 'to="--to=tcp:localhost:$KITTY_PORT "' 'to=""' 86 | move='kitty @ ${to}kitten neighboring_window.py' 87 | 88 | # Key Binds 89 | bind-key -n 'C-h' if-shell "[ #{pane_at_left} != 1 ]" "select-pane -L" "run-shell '$move left'" 90 | bind-key -n 'C-j' if-shell "[ #{pane_at_bottom} != 1 ]" "select-pane -D" "run-shell '$move bottom'" 91 | bind-key -n 'C-k' if-shell "[ #{pane_at_top} != 1 ]" "select-pane -U" "run-shell '$move top'" 92 | bind-key -n 'C-l' if-shell "[ #{pane_at_right} != 1 ]" "select-pane -R" "run-shell '$move right'" 93 | ``` 94 | 95 | SSH Compatibility 96 | ----------------- 97 | 98 | With the settings above, navigation should work well locally. But if you need kitty-tmux navigation also work through ssh, follow steps below: 99 | 100 | 1. Install kitty on your remote machine. [How To](https://sw.kovidgoyal.net/kitty/binary.html?highlight=install). 101 | With kitty installed on your remote system and remote control enabled, you should be able to use remote control from ssh. But because of reasons explained [here](https://github.com/kovidgoyal/kitty/issues/2338), it won't work from inside tmux. So we're going to need some workarounds. 102 | 103 | 2. Set remote port forwarding when using SSH. 104 | 105 | ```sh 106 | ssh -R 50000:${KITTY_LISTEN_ON#*:} user@host 107 | ``` 108 | 109 | The remote TCP port 50000 can be changed to anything depending on your needs. 110 | 111 | You can put it as an alias on your shell rc file so you don't type it all the time. 112 | 113 | ```sh 114 | alias ssh='ssh -R 50000:${KITTY_LISTEN_ON#*:}' 115 | ``` 116 | 117 | 3. Add the following snippet to your remote machine's `.bashrc` or something similar on other shell: 118 | 119 | ```sh 120 | # Source kitty binary 121 | export PATH=$PATH:~/.local/kitty.app/bin/ 122 | 123 | # Set KITTY_PORT env variable 124 | if [[ $SSH_TTY ]] && ! [ -n "$TMUX" ]; then 125 | export KITTY_PORT=`kitty @ ls 2>/dev/null | grep "[0-9]:/tmp/mykitty" | head -n 1 | cut -d : -f 1 | cut -d \" -f 2` 126 | fi 127 | 128 | # Kitty Terminal Navigation 129 | bind -x '"\C-h": kitty @ kitten neighboring_window.py left' 130 | bind -x '"\C-j": kitty @ kitten neighboring_window.py top' 131 | bind -x '"\C-k": kitty @ kitten neighboring_window.py bottom' 132 | bind -x '"\C-l": kitty @ kitten neighboring_window.py right' 133 | ``` 134 | 135 | **Explanation**: 136 | - Source kitty path so the binary can be called from anywhere 137 | - Set the KITTY_PORT environment variable automatically from the foreground process that call ssh. 138 | - Set terminal key binding for changing kitty window when not using tmux 139 | 140 | 4. Don't forget to install the tmux plugin on your remote system also. And copy `neighboring_window.py` to your remote machine ~/.config/kitty directory. 141 | --------------------------------------------------------------------------------