├── .gitignore ├── README.md ├── bin ├── i3_tmux_vim_focus └── i3_tmux_vim_focus.c ├── doc └── i3wm-tmux-navigator.txt ├── pattern-check └── plugin └── i3wm_tmux_navigator.vim /.gitignore: -------------------------------------------------------------------------------- 1 | doc/tags 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Vim - i3wm - Tmux Navigator 2 | ================== 3 | 4 | This plugin is a repackaging of [Mislav Marohnić's][] tmux-navigator 5 | configuration described in [this gist][]. When combined with sets of tmux and i3wm 6 | key bindings, the plugin will allow you to navigate seamlessly between 7 | system windows,vim and tmux splits using a consistent set of hotkeys. 8 | 9 | **NOTE**: This requires tmux v1.8 or higher. 10 | **NOTE** For proper i3wm-tmux-vim configuration, please see [this commit](https://github.com/fogine/dotfiles/commit/822f9675deede87a1abc82464272f2612e583227) of my dotfiles. 11 | Also you will need [THIS](https://github.com/fogine/dotfiles/blob/master/bin/tmux_sys_win_aware_navigation.sh) script in your path together with [THIS](https://github.com/fogine/vim-i3wm-tmux-navigator/tree/master/bin) binary executable. 12 | I had it working with tmux 2.0 however after a while I disabled it as it is faster for me to navigate between system windows with win+h/j/k/l, instead of ctrl+h/j/k/l 13 | 14 | Usage 15 | ----- 16 | 17 | This plugin provides the following mappings which allow you to move between 18 | Vim panes and tmux splits seamlessly. 19 | 20 | - `` => Left 21 | - `` => Down 22 | - `` => Up 23 | - `` => Right 24 | - `` => Previous split 25 | 26 | **Note** - you don't need to use your tmux `prefix` key sequence before using 27 | the mappings. 28 | 29 | If you want to use alternate key mappings, see the [configuration section 30 | below][]. 31 | 32 | Installation 33 | ------------ 34 | 35 | ### Vim 36 | 37 | If you don't have a preferred installation method, I recommend using [Vundle][]. 38 | Assuming you have Vundle installed and configured, the following steps will 39 | install the plugin: 40 | 41 | Add the following line to your `~/.vimrc` file 42 | 43 | ``` vim 44 | Plugin 'christoomey/vim-tmux-navigator' 45 | ``` 46 | 47 | Then run 48 | 49 | ``` 50 | :PluginInstall 51 | ``` 52 | 53 | ### Tmux 54 | 55 | Add the following to your `tmux.conf` file to configure the tmux side of 56 | this customization. 57 | 58 | ``` tmux 59 | # Smart pane switching with awareness of vim splits 60 | is_vim='echo "#{pane_current_command}" | grep -iqE "(^|\/)g?(view|n?vim?)(diff)?$"' 61 | bind -n C-h if-shell "$is_vim" "send-keys C-h" "select-pane -L" 62 | bind -n C-j if-shell "$is_vim" "send-keys C-j" "select-pane -D" 63 | bind -n C-k if-shell "$is_vim" "send-keys C-k" "select-pane -U" 64 | bind -n C-l if-shell "$is_vim" "send-keys C-l" "select-pane -R" 65 | bind -n C-\ if-shell "$is_vim" "send-keys C-\\" "select-pane -l" 66 | ``` 67 | 68 | Thanks to Christopher Sexton who provided the updated tmux configuration in 69 | [this blog post][]. 70 | 71 | Configuration 72 | ------------- 73 | 74 | ### Custom Key Bindings 75 | 76 | If you don't want the plugin to create any mappings, you can use the five 77 | provided functions to define your own custom maps. You will need to define 78 | custom mappings in your `~/.vimrc` as well as update the bindings in tmux to 79 | match. 80 | 81 | #### Vim 82 | 83 | Add the following to your `~/.vimrc` to define your custom maps: 84 | 85 | ``` vim 86 | let g:tmux_navigator_no_mappings = 1 87 | 88 | nnoremap {Left-mapping} :TmuxNavigateLeft 89 | nnoremap {Down-Mapping} :TmuxNavigateDown 90 | nnoremap {Up-Mapping} :TmuxNavigateUp 91 | nnoremap {Right-Mapping} :TmuxNavigateRight 92 | nnoremap {Previous-Mapping} :TmuxNavigatePrevious 93 | ``` 94 | 95 | *Note* Each instance of `{Left-Mapping}` or `{Down-Mapping}` must be replaced 96 | in the above code with the desired mapping. Ie, the mapping for `` => 97 | Left would be created with `nnoremap :TmuxNavigateLeft`. 98 | 99 | 100 | ##### Autosave on leave 101 | 102 | let g:tmux_navigator_save_on_switch = 1 103 | 104 | This will execute the update command on leaving vim to a tmux pane. Default is Zero 105 | 106 | 107 | #### Tmux 108 | 109 | Alter each of the five lines of the tmux configuration listed above to use your 110 | custom mappings. **Note** each line contains two references to the desired 111 | mapping. 112 | 113 | ### Additional Customization 114 | 115 | #### Restoring Clear Screen (C-l) 116 | 117 | The default key bindings include `` which is the readline key binding 118 | for clearing the screen. The following binding can be added to your `~/.tmux.conf` file to provide an alternate mapping to `clear-screen`. 119 | 120 | ``` tmux 121 | bind C-l send-keys 'C-l' 122 | ``` 123 | 124 | With this enabled you can use ` C-l` to clear the screen. 125 | 126 | Thanks to [Brian Hogan][] for the tip on how to re-map the clear screen binding. 127 | 128 | #### Nesting 129 | If you like to nest your tmux sessions, this plugin is not going to work 130 | properly. It probably never will, as it would require detecting when Tmux would 131 | wrap from one outermost pane to another and propagating that to the outer 132 | session. 133 | 134 | By default this plugin works on the outermost tmux session and the vim 135 | sessions it contains, but you can customize the behaviour by adding more 136 | commands to the expression used by the grep command. 137 | 138 | When nesting tmux sessions via ssh or mosh, you could extend it to look like 139 | `'(^|\/)g?(view|vim|ssh|mosh?)(diff)?$'`, which makes this plugin work within 140 | the innermost tmux session and the vim sessions within that one. This works 141 | better than the default behaviour if you use the outer Tmux sessions as relays 142 | to different hosts and have all instances of vim on remote hosts. 143 | 144 | Similarly, if you like to nest tmux locally, add `|tmux` to the expression. 145 | 146 | This behaviour means that you can't leave the innermost session with Ctrl-hjkl 147 | directly. These following fallback mappings can be targeted to the right Tmux 148 | session by escaping the prefix (Tmux' `send-prefix` command). 149 | 150 | ``` tmux 151 | bind -r C-h run "tmux select-pane -L" 152 | bind -r C-j run "tmux select-pane -D" 153 | bind -r C-k run "tmux select-pane -U" 154 | bind -r C-l run "tmux select-pane -R" 155 | bind -r C-\ run "tmux select-pane -l" 156 | ``` 157 | 158 | Troubleshooting 159 | --------------- 160 | 161 | ### Vim -> Tmux doesn't work! 162 | 163 | This is likely due to conflicting key mappings in your `~/.vimrc`. You can use 164 | the following search pattern to find conflicting mappings 165 | `\vn(nore)?map\s+\`. Any matching lines should be deleted or 166 | altered to avoid conflicting with the mappings from the plugin. 167 | 168 | Another option is that the pattern matching included in the `.tmux.conf` is 169 | not recognizing that Vim is active. To check that tmux is properly recognizing 170 | Vim, use the provided Vim command `:TmuxPaneCurrentCommand`. The output of 171 | that command should be a string like 'vim', 'Vim', 'vimdiff', etc. If you 172 | encounter a different output please [open an issue][] with as much info about 173 | your OS, Vim version, and tmux version as possible. 174 | 175 | [open an issue]: https://github.com/christoomey/vim-tmux-navigator/issues/new 176 | 177 | ### Tmux Can't Tell if Vim Is Active 178 | 179 | This functionality requires tmux version 1.8 or higher. You can check your 180 | version to confirm with this shell command: 181 | 182 | ``` bash 183 | tmux -V # should return 'tmux 1.8' 184 | ``` 185 | 186 | ### Switching out of Vim Is Slow 187 | 188 | If you find that navigation within Vim (from split to split) is fine, but Vim 189 | to a non-Vim tmux pane is delayed, it might be due to a slow shell startup. 190 | Consider moving code from your shell's non-interactive rc file (e.g., 191 | `~/.zshenv`) into the interactive startup file (e.g., `~/.zshrc`) as Vim only 192 | sources the non-interactive config. 193 | 194 | ### It Doesn't Work in tmate 195 | 196 | [tmate][] is a tmux fork that aids in setting up remote pair programming 197 | sessions. It is designed to run alongside tmux without issue, but occasionally 198 | there are hiccups. Specifically, if the versions of tmux and tmate don't match, 199 | you can have issues. See [this 200 | issue](https://github.com/christoomey/vim-tmux-navigator/issues/27) for more 201 | detail. 202 | 203 | [tmate]: http://tmate.io/ 204 | 205 | ### It Still Doesn't Work!!! 206 | 207 | The tmux configuration uses an inlined grep pattern match to help determine if 208 | the current pane is running Vim. If you run into any issues with the navigation 209 | not happening as expected, you can try using [Mislav's original external 210 | script][] which has a more robust check. 211 | 212 | [Brian Hogan]: https://twitter.com/bphogan 213 | [Mislav Marohnić's]: http://mislav.uniqpath.com/ 214 | [Mislav's original external script]: https://github.com/mislav/dotfiles/blob/master/bin/tmux-vim-select-pane 215 | [Vundle]: https://github.com/gmarik/vundle 216 | [configuration section below]: #custom-key-bindings 217 | [this blog post]: http://www.codeography.com/2013/06/19/navigating-vim-and-tmux-splits 218 | [this gist]: https://gist.github.com/mislav/5189704 219 | -------------------------------------------------------------------------------- /bin/i3_tmux_vim_focus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogine/vim-i3wm-tmux-navigator/3e10bdc8bc9c0af8f4eee89ec7b65be1eac0dcc2/bin/i3_tmux_vim_focus -------------------------------------------------------------------------------- /bin/i3_tmux_vim_focus.c: -------------------------------------------------------------------------------- 1 | /* File: i3_vim_focus.c 2 | * 3 | * Compile with: 4 | * gcc i3_tmux_vim_focus.c -lX11 -lxdo -o i3_tmux_vim_focus $(pkg-config --libs --cflags i3ipc-glib-1.0) 5 | * 6 | */ 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | int main(int argc, char *argv[]) { 19 | 20 | char cmd[20]; 21 | 22 | unsigned char *name; 23 | int name_len; 24 | int name_type; 25 | Window window_ret; 26 | 27 | i3ipcConnection *conn; 28 | gchar *reply; 29 | 30 | if(argc < 2){ 31 | printf("Missing argument\n"); 32 | return 1; 33 | } 34 | 35 | xdo_t *xdo = xdo_new(NULL); 36 | xdo_get_active_window(xdo, &window_ret); 37 | xdo_get_window_name(xdo, window_ret, &name, &name_len, &name_type); 38 | 39 | if(strstr(name, "VIM") || strstr(name, "vim")) 40 | { 41 | strcpy(cmd, "Escape+g+w+"); 42 | 43 | 44 | strcat(cmd, (argv[1][0] == 'l')? "h" : 45 | (argv[1][0] == 'd')? "j" : 46 | (argv[1][0] == 'u')? "k" : 47 | "l" ); 48 | 49 | xdo_send_keysequence_window(xdo, window_ret, cmd, 0); 50 | } 51 | else if(strstr(name, "tmux")) 52 | { 53 | strcpy(cmd, "ctrl+a"); 54 | 55 | 56 | xdo_send_keysequence_window(xdo, window_ret, cmd, 0); 57 | strcpy(cmd, (argv[1][0] == 'l')? "Left" : 58 | (argv[1][0] == 'd')? "Down" : 59 | (argv[1][0] == 'u')? "Up" : 60 | "Right" ); 61 | 62 | xdo_send_keysequence_window(xdo, window_ret, cmd, 0); 63 | } 64 | else 65 | { 66 | conn = i3ipc_connection_new(NULL, NULL); 67 | strcpy(cmd, "focus "); 68 | strcat(cmd, argv[1]); 69 | reply = i3ipc_connection_message(conn, I3IPC_MESSAGE_TYPE_COMMAND, cmd, NULL); 70 | g_free(reply); 71 | g_object_unref(conn); 72 | } 73 | 74 | XFree(name); 75 | return 0; 76 | } 77 | -------------------------------------------------------------------------------- /doc/i3wm-tmux-navigator.txt: -------------------------------------------------------------------------------- 1 | *tmux-navigator.txt* Plugin to allow seamless navigation between tmux and vim 2 | 3 | ============================================================================== 4 | CONTENTS *tmux-navigator-contents* 5 | 6 | 7 | ============================================================================== 8 | INTRODUCTION *tmux-navigator* 9 | 10 | Vim-tmux-navigator is a little plugin which enables seamless navigation 11 | between tmux panes and vim splits. This plugin is a repackaging of Mislav 12 | Marohinc's tmux=navigator configuration. When combined with a set of tmux key 13 | bindings, the plugin will allow you to navigate seamlessly between vim and 14 | tmux splits using a consistent set of hotkeys. 15 | 16 | NOTE: This requires tmux v1.8 or higher. 17 | 18 | ============================================================================== 19 | CONFIGURATION *tmux-navigator-configuration* 20 | 21 | * Activate autoupdate on exit 22 | let g:tmux_navigator_save_on_switch = 1 23 | 24 | * Custom Key Bindings 25 | let g:tmux_navigator_no_mappings = 1 26 | 27 | nnoremap {Left-mapping} :TmuxNavigateLeft 28 | nnoremap {Down-Mapping} :TmuxNavigateDown 29 | nnoremap {Up-Mapping} :TmuxNavigateUp 30 | nnoremap {Right-Mapping} :TmuxNavigateRight 31 | nnoremap {Previous-Mapping} :TmuxNavigatePrevious 32 | 33 | vim:tw=78:ts=8:ft=help:norl: 34 | -------------------------------------------------------------------------------- /pattern-check: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Collection of various test strings that could be the output of the tmux 4 | # 'pane_current_comamnd' message. Included as regression test for updates to 5 | # the inline grep pattern used in the `.tmux.conf` configuration 6 | 7 | set -e 8 | 9 | RED=$(tput setaf 1) 10 | GREEN=$(tput setaf 2) 11 | YELLOW=$(tput setaf 3) 12 | NORMAL=$(tput sgr0) 13 | 14 | vim_pattern='(^|\/)g?(view|n?vim?)(diff)?$' 15 | match_tests=(vim Vim VIM vimdiff /usr/local/bin/vim vi gvim view gview nvim) 16 | no_match_tests=( /Users/christoomey/.vim/thing /usr/local/bin/start-vim ) 17 | 18 | display_matches() { 19 | for process_name in "$@"; do 20 | printf "%s %s\n" "$(matches_vim_pattern $process_name)" "$process_name" 21 | done 22 | } 23 | 24 | matches_vim_pattern() { 25 | if echo "$1" | grep -iqE "$vim_pattern"; then 26 | echo "${GREEN}match${NORMAL}" 27 | else 28 | echo "${RED}fail${NORMAL}" 29 | fi 30 | } 31 | 32 | main() { 33 | echo "Testing against pattern: ${YELLOW}$vim_pattern${NORMAL}\n" 34 | 35 | echo "These should all ${GREEN}match${NORMAL}\n----------------------" 36 | display_matches "${match_tests[@]}" 37 | 38 | echo "\nThese should all ${RED}fail${NORMAL}\n---------------------" 39 | display_matches "${no_match_tests[@]}" 40 | } 41 | 42 | main 43 | -------------------------------------------------------------------------------- /plugin/i3wm_tmux_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 tmux. 3 | " Additionally, toggles between last active vim splits/tmux panes. 4 | 5 | if exists("g:loaded_tmux_navigator") || &cp || v:version < 700 6 | finish 7 | endif 8 | let g:loaded_tmux_navigator = 1 9 | 10 | if !exists("g:tmux_navigator_save_on_switch") 11 | let g:tmux_navigator_save_on_switch = 0 12 | endif 13 | 14 | function! s:UseTmuxNavigatorMappings() 15 | return !exists("g:tmux_navigator_no_mappings") || !g:tmux_navigator_no_mappings 16 | endfunction 17 | 18 | function! s:InTmuxSession() 19 | return $TMUX != '' 20 | endfunction 21 | 22 | function! s:TmuxPaneCurrentCommand() 23 | echo system("tmux display-message -p '#{pane_current_command}'") 24 | endfunction 25 | command! TmuxPaneCurrentCommand call TmuxPaneCurrentCommand() 26 | 27 | let s:tmux_is_last_pane = 0 28 | au WinEnter * let s:tmux_is_last_pane = 0 29 | 30 | " Like `wincmd` but also change tmux panes instead of vim windows when needed. 31 | function! s:TmuxWinCmd(direction) 32 | if s:InTmuxSession() 33 | call s:TmuxAwareNavigate(a:direction) 34 | else 35 | echom "Vim navigate" 36 | let oldw = winnr() 37 | call s:VimNavigate(a:direction) 38 | 39 | if oldw == winnr() 40 | echom "vim system navigate" 41 | call s:SystemWindowNavigate(a:direction) 42 | endif 43 | endif 44 | endfunction 45 | 46 | function! s:NeedsVitalityRedraw() 47 | return exists('g:loaded_vitality') && v:version < 704 && !has("patch481") 48 | endfunction 49 | 50 | function! s:TmuxGetActivePaneId() 51 | let cmd = "tmux list-panes -F '#P #{?pane_active,active,}'" 52 | let list = split(system(cmd), '\n') 53 | let paneID = '' 54 | 55 | for pane in list 56 | if match(pane, 'active') != -1 57 | let paneID = pane 58 | endif 59 | endfor 60 | return paneID 61 | endfunction 62 | 63 | function! s:TmuxAwareNavigate(direction) 64 | 65 | let nr = winnr() 66 | "let tmux_pane = '' 67 | "let tmux_pane = s:TmuxGetActivePaneId() 68 | let tmux_last_pane = (a:direction == 'p' && s:tmux_is_last_pane) 69 | if !tmux_last_pane 70 | call s:VimNavigate(a:direction) 71 | endif 72 | 73 | 74 | " Forward the switch panes command to tmux if: 75 | " a) we're toggling between the last tmux pane; 76 | " b) we tried switching windows in vim but it didn't have effect. 77 | if tmux_last_pane || nr == winnr() 78 | if g:tmux_navigator_save_on_switch 79 | update 80 | endif 81 | 82 | let i3_comando = '' 83 | 84 | if a:direction == 'h' 85 | let i3_comando = "left" 86 | elseif a:direction == 'j' 87 | let i3_comando = "down" 88 | elseif a:direction == 'k' 89 | let i3_comando = "up" 90 | elseif a:direction == 'l' 91 | let i3_comando = "right" 92 | elseif a:direction == 'p' 93 | finish 94 | endif 95 | "let cmd = 'tmux select-pane -' . tr(a:direction, 'phjkl', 'lLDUR') 96 | let cmd = 'tmux_sys_win_aware_navigation.sh '. i3_comando 97 | silent call system(cmd) 98 | let output= system("tmux run-shell 'tmux rename-window #{pane_current_command}'") 99 | 100 | "if tmux_pane == s:TmuxGetActivePaneId() 101 | "call s:SystemWindowNavigate(a:direction) 102 | "endif 103 | 104 | if s:NeedsVitalityRedraw() 105 | redraw! 106 | endif 107 | let s:tmux_is_last_pane = 1 108 | else 109 | let s:tmux_is_last_pane = 0 110 | endif 111 | endfunction 112 | 113 | function! s:VimNavigate(direction) 114 | try 115 | execute 'wincmd ' . a:direction 116 | catch 117 | echohl ErrorMsg | echo 'E11: Invalid in command-line window; executes, CTRL-C quits: wincmd k' | echohl None 118 | endtry 119 | endfunction 120 | 121 | func! s:SystemWindowNavigate(comando) 122 | let i3_comando = '' 123 | 124 | if a:comando == 'h' 125 | let i3_comando = "left" 126 | elseif a:comando == 'j' 127 | let i3_comando = "down" 128 | elseif a:comando == 'k' 129 | let i3_comando = "up" 130 | elseif a:comando == 'l' 131 | let i3_comando = "right" 132 | elseif a:comando == 'p' 133 | finish 134 | endif 135 | 136 | call system('i3-msg -q focus ' . i3_comando) 137 | if !has("gui_running") 138 | redraw! 139 | endif 140 | endfunction 141 | 142 | command! TmuxNavigateLeft call TmuxWinCmd('h') 143 | command! TmuxNavigateDown call TmuxWinCmd('j') 144 | command! TmuxNavigateUp call TmuxWinCmd('k') 145 | command! TmuxNavigateRight call TmuxWinCmd('l') 146 | command! TmuxNavigatePrevious call TmuxWinCmd('p') 147 | 148 | if s:UseTmuxNavigatorMappings() 149 | nnoremap :TmuxNavigateLeft 150 | nnoremap :TmuxNavigateDown 151 | nnoremap :TmuxNavigateUp 152 | nnoremap :TmuxNavigateRight 153 | nnoremap :TmuxNavigatePrevious 154 | endif 155 | --------------------------------------------------------------------------------