├── 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 |
--------------------------------------------------------------------------------