├── README.md ├── VERSION.md ├── plugin └── tmux-navigate.vim └── tmux-navigate.tmux /README.md: -------------------------------------------------------------------------------- 1 | # tmux-navigate 2 | 3 | Intelligently navigate tmux panes and Vim splits using the same keys. 4 | This also supports SSH tunnels where Vim is running on a remote host. 5 | 6 | | inside Vim? | is Zoomed? | Action taken by key binding | 7 | | ----------- | ---------- | --------------------------- | 8 | | No | No | Focus directional tmux pane | 9 | | No | Yes | Nothing: ignore key binding | 10 | | Yes | No | Seamlessly focus Vim / tmux | 11 | | Yes | Yes | Focus directional Vim split | 12 | 13 | See https://sunaku.github.io/tmux-select-pane.html for documentation. 14 | 15 | ## Installation 16 | 17 | 1. Install the [TPM] framework for tmux. 18 | 19 | [TPM]: https://github.com/tmux-plugins/tpm 20 | 21 | 2. Add this line to your `~/.tmux.conf`: 22 | ```sh 23 | set -g @plugin 'sunaku/tmux-navigate' 24 | ``` 25 | 26 | 3. Configure your navigation shortcuts: 27 | ```sh 28 | # if you're using QWERTY layout 29 | set -g @navigate-left '-n M-h' 30 | set -g @navigate-down '-n M-j' 31 | set -g @navigate-up '-n M-k' 32 | set -g @navigate-right '-n M-l' 33 | set -g @navigate-back '-n M-\' 34 | 35 | # if you're using DVORAK layout 36 | set -g @navigate-back '-n M-d' 37 | set -g @navigate-left '-n M-h' 38 | set -g @navigate-up '-n M-t' 39 | set -g @navigate-down '-n M-n' 40 | set -g @navigate-right '-n M-s' 41 | ``` 42 | 43 | 4. Reload your tmux configuration file. 44 | 45 | 5. Type prefix+I. 46 | (This makes TPM install the plugin.) 47 | 48 | ### Vim integration 49 | 50 | > Option 1: use your favorite Vim plugin manager 51 | ```vim 52 | Plug 'sunaku/tmux-navigate' 53 | ``` 54 | 55 | > Option 2: symlink from your tmux plugins clone 56 | ```sh 57 | mkdir -p ~/.vim/plugin/ 58 | ln -s ~/.tmux/plugins/tmux-navigate/plugin/tmux-navigate.vim ~/.vim/plugin/ 59 | ``` 60 | 61 | ## License 62 | 63 | [Spare A Life]: https://sunaku.github.io/vegan-for-life.html 64 | > Like my work? 👍 Please [spare a life] today as thanks! 🐄🐖🐑🐔🐣🐟✨🙊✌ 65 | > Why? For 💕 ethics, the 🌎 environment, and 💪 health; see link above. 🙇 66 | 67 | (the ISC license) 68 | 69 | Copyright 2018 Suraj N. Kurapati 70 | 71 | Permission to use, copy, modify, and/or distribute this software for any 72 | purpose with or without fee is hereby granted, provided that the above 73 | copyright notice and this permission notice appear in all copies. 74 | 75 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 76 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 77 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 78 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 79 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 80 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 81 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 82 | -------------------------------------------------------------------------------- /VERSION.md: -------------------------------------------------------------------------------- 1 | ## Version 0.2.0 (2020-07-10) 2 | 3 | Minor: 4 | 5 | * Removed `@navigate-timeout` configuration setting. 6 | 7 | I have implemented proper edge detection (no more timeouts!) in the Vim 8 | portion of this plugin by reporting navigable directions in Vim's title. 9 | 10 | Other: 11 | 12 | * Moved tmux navigation logic out of string literal. 13 | 14 | * Added documentation comments; improved formatting. 15 | 16 | ## Version 0.1.1 (2020-05-06) 17 | 18 | Patch: 19 | 20 | * README: Vim integration required for local control. 21 | 22 | Since we rely on pane title changes to check whether Vim navigation was 23 | successful, the included Vim plugin is also necessary for local control 24 | (it's not only for remote control, where we connect to Vim through SSH). 25 | 26 | * GH-1: don't send BSpace upon Vim misidentification. 27 | 28 | For example, Vim 7.2 sets the pane title to "Thanks for flying Vim" and 29 | doesn't bother to clean up after itself upon termination. As a result, 30 | the old navigation logic would still see "Thanks for flying Vim" as the 31 | title and think that Vim was still running: therefore misidentification. 32 | 33 | ## Version 0.1.0 (2020-04-25) 34 | 35 | Minor: 36 | 37 | * Release [blog snippet] as a proper [TPM] plugin: `tmux-navigate`. 38 | 39 | Thanks to @bradleyharden for contributing a working example of TPM 40 | plugin conversion and giving me the opportunity to host this repo. 41 | 42 | [TPM]: https://github.com/tmux-plugins/tpm 43 | [blog snippet]: https://sunaku.github.io/tmux-select-pane.html 44 | -------------------------------------------------------------------------------- /plugin/tmux-navigate.vim: -------------------------------------------------------------------------------- 1 | " 2 | " Intelligently navigate tmux panes and Vim splits using the same keys. 3 | " This also supports SSH tunnels where Vim is running on a remote host. 4 | " 5 | " See https://sunaku.github.io/tmux-select-pane.html for documentation. 6 | 7 | function! TmuxNavigateDirections() abort 8 | let [y, x] = win_screenpos('.') 9 | let h = winheight('.') 10 | let w = winwidth('.') 11 | 12 | let can_go_up = y > 2 " +1 for the tabline 13 | let can_go_down = y + h < &lines - &laststatus 14 | let can_go_left = x > 1 15 | let can_go_right = x + w < &columns 16 | 17 | return 18 | \ (can_go_up ? 'U' : '') . 19 | \ (can_go_down ? 'D' : '') . 20 | \ (can_go_left ? 'L' : '') . 21 | \ (can_go_right ? 'R' : '') 22 | endfunction 23 | 24 | let progname = substitute($VIM, '.*[/\\]', '', '') 25 | set title titlestring=%{progname}\ %f\ #%{TmuxNavigateDirections()} 26 | 27 | " enable support for setting the window title in regular Vim under tmux 28 | if &term =~ '^screen' && !has('nvim') 29 | execute "set t_ts=\e]2; t_fs=\7" 30 | endif 31 | -------------------------------------------------------------------------------- /tmux-navigate.tmux: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Intelligently navigate tmux panes and Vim splits using the same keys. 4 | # This also supports SSH tunnels where Vim is running on a remote host. 5 | # 6 | # +-------------+------------+-----------------------------+ 7 | # | inside Vim? | is Zoomed? | Action taken by key binding | 8 | # +-------------+------------+-----------------------------+ 9 | # | No | No | Focus directional tmux pane | 10 | # | No | Yes | Nothing: ignore key binding | 11 | # | Yes | No | Seamlessly focus Vim / tmux | 12 | # | Yes | Yes | Focus directional Vim split | 13 | # +-------------+------------+-----------------------------+ 14 | # 15 | # See https://sunaku.github.io/tmux-select-pane.html for documentation. 16 | 17 | get_tmux_option() { tmux show-option -gqv "$@" | grep . ;} 18 | 19 | navigate=$(sed '1,/^exit #.*$/d; s/^ *#.*//; /^$/d' "$0") 20 | navigate_left=" $navigate L 'tmux select-pane -L' 'tmux send-keys C-w h'" 21 | navigate_down=" $navigate D 'tmux select-pane -D' 'tmux send-keys C-w j'" 22 | navigate_up=" $navigate U 'tmux select-pane -U' 'tmux send-keys C-w k'" 23 | navigate_right="$navigate R 'tmux select-pane -R' 'tmux send-keys C-w l'" 24 | navigate_back=" $navigate l 'tmux select-pane -l || tmux select-pane -t1'\ 25 | 'tmux send-keys C-w p' \ 26 | 'pane_is_zoomed' " 27 | 28 | for direction in left down up right back; do 29 | option="@navigate-$direction" 30 | handler="navigate_$direction" 31 | if key=$(get_tmux_option "$option"); then 32 | eval "action=\$$handler" # resolve handler variable 33 | tmux bind-key $key run-shell -b ": $option; $action" 34 | fi 35 | done 36 | 37 | exit #------------------------------------------------------------------------ 38 | 39 | # interpolate tmux values ONCE at "compile time" 40 | # (this is the reason for the double ## escapes) 41 | pane_title="#{q:pane_title}" 42 | pane_current_command="#{q:pane_current_command}" 43 | window_zoomed_flag=#{window_zoomed_flag} 44 | 45 | pane_is_zoomed() { 46 | test $window_zoomed_flag -eq 1 47 | } 48 | 49 | command_is_vim() { 50 | case "${1%% *}" in 51 | (vi|?vi|vim*|?vim*|view|?view|vi??*) 52 | true 53 | ;; 54 | (*) 55 | false 56 | ;; 57 | esac 58 | } 59 | 60 | pane_contains_vim() { 61 | command_is_vim "$pane_current_command" || 62 | command_is_vim "$pane_title" 63 | } 64 | 65 | pane_contains_neovim_terminal() { 66 | case "$pane_title" in 67 | (nvim?term://*) 68 | true 69 | ;; 70 | (*) 71 | false 72 | ;; 73 | esac 74 | } 75 | 76 | navigate() { 77 | tmux_navigation_direction=$1 78 | tmux_navigation_command=$2 79 | vim_navigation_command=$3 80 | vim_navigation_only_if=${4:-true} 81 | 82 | # try navigating Vim 83 | if pane_contains_vim && eval "$vim_navigation_only_if"; then 84 | 85 | # parse navigable directions from Vim's title 86 | vim_navigable_directions=${pane_title####* } 87 | 88 | # if desired direction is navigable in Vim... 89 | case "l$vim_navigable_directions" in (*$tmux_navigation_direction*) 90 | 91 | # leave insert mode in NeoVim terminal 92 | if pane_contains_neovim_terminal; then 93 | tmux send-keys C-\\ C-n 94 | fi 95 | 96 | # navigate Vim and don't fall through 97 | eval "$vim_navigation_command" 98 | return 99 | 100 | ;; 101 | esac 102 | 103 | # otherwise fall through into tmux navigation 104 | fi 105 | 106 | # try navigating tmux 107 | if ! pane_is_zoomed; then 108 | eval "$tmux_navigation_command" 109 | fi 110 | } 111 | 112 | navigate 113 | --------------------------------------------------------------------------------