-
Notifications
You must be signed in to change notification settings - Fork 31
/
tilish.tmux
executable file
·295 lines (261 loc) · 10.1 KB
/
tilish.tmux
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
#!/bin/sh
# vim: foldmethod=marker noexpandtab tabstop=4
# Project: tmux-tilish
# Author: Jabir Ali Ouassou <[email protected]>
# Licence: MIT licence
#
# This file contains the `tmux` plugin `tilish`, which implements keybindings
# that turns `tmux` into a more typical tiling window manger for your terminal.
# The keybindings are taken nearly directly from `i3wm` and `sway`, but with
# minor adaptation to fit better with `vim` and `tmux`. See also the README.
# shellcheck disable=SC2016
# shellcheck disable=SC2086
# shellcheck disable=SC2250
# Check input parameters {{{
# Whether we need to use legacy workarounds (required before Tmux 2.7).
legacy="$(tmux -V | grep -E 'tmux (1\.|2\.[0-6])')"
# Read user options.
for opt in default dmenu easymode enforce navigate navigator prefix project remap shiftnum; do
export "$opt"="$(tmux show-option -gv @tilish-"$opt" 2>/dev/null)"
done
# Default to US keyboard layout, unless something is configured.
if [ -z "$shiftnum" ]; then
shiftnum='!@#$%^&*()'
fi
# Define placeholder variables used in keybindings below.
h='h'; j='j'; k='k'; l='l'; o='o';
H='H'; J='J'; K='K'; L='L'; O='O';
s='s'; S='S'; v='v'; V='V'; t='t'; z='z';
d='d'; n='n'; p='p'; r='r';
C='C'; E='E'; Q='Q';
enter='enter'
# Let the user redefine these variables to remap keys.
if [ -n "${remap:-}" ]; then
eval "${remap}"
fi
# If easymode is on, use arrow keys instead of Vim-style hjkl.
if [ "${easymode:-}" = "on" ]; then
# Simplified arrows.
h='left'; j='down'; k='up'; l='right';
H='S-left'; J='S-down'; K='S-up'; L='S-right';
fi
# Determine modifier vs. prefix key.
if [ -z "${prefix:-}" ]; then
bind='bind -n'
mod='M-'
else
bind='bind -rT tilish'
mod=''
fi
# }}}
# Define core functionality {{{
bind_switch() {
# Bind keys to switch to a workspace. The workspace is created if it
# doesn't exist, and if we're already there we go back to the last one.
tmux $bind "$1" \
if-shell '[ "$(tmux display -p "#I")" != "'"$2"'" ]' \
"if-shell 'tmux select-window -t :$2' '' 'new-window -t :$2'" \
"last-window"
}
bind_move() {
# Bind keys to move panes between workspaces.
if [ -z "$legacy" ]; then
tmux $bind "$1" \
if-shell "tmux join-pane -t :$2" \
"" \
"new-window -dt :$2; join-pane -t :$2; select-pane -t top-left; kill-pane" \\\; select-layout -E
else
tmux $bind "$1" \
if-shell "tmux new-window -dt :$2" \
"join-pane -t :$2; select-pane -t top-left; kill-pane" \
"send escape; join-pane -t :$2" \\\; select-layout
fi
}
bind_layout() {
# Bind keys to switch or refresh layouts.
if [ "$2" = "zoom" ]; then
# Invoke the zoom feature.
tmux $bind "$1" \
resize-pane -Z
else
# Actually switch layout.
if [ -z "$legacy" ]; then
tmux $bind "$1" \
select-layout "$2" \\\; select-layout -E
else
tmux $bind "$1" \
run-shell "tmux select-layout \"$2\"" \\\; send escape
fi
fi
}
char_at() {
# Finding the character at a given position in
# a string in a way compatible with POSIX sh.
echo $1 | cut -c $2
}
# }}}
# Define keybindings {{{
# Define a prefix key.
if [ -n "$prefix" ]; then
tmux bind -n "$prefix" switch-client -T tilish
fi
# Switch to workspace via Alt + #.
bind_switch "${mod}1" 1
bind_switch "${mod}2" 2
bind_switch "${mod}3" 3
bind_switch "${mod}4" 4
bind_switch "${mod}5" 5
bind_switch "${mod}6" 6
bind_switch "${mod}7" 7
bind_switch "${mod}8" 8
bind_switch "${mod}9" 9
# Move pane to workspace via Alt + Shift + #.
bind_move "${mod}$(char_at $shiftnum 1)" 1
bind_move "${mod}$(char_at $shiftnum 2)" 2
bind_move "${mod}$(char_at $shiftnum 3)" 3
bind_move "${mod}$(char_at $shiftnum 4)" 4
bind_move "${mod}$(char_at $shiftnum 5)" 5
bind_move "${mod}$(char_at $shiftnum 6)" 6
bind_move "${mod}$(char_at $shiftnum 7)" 7
bind_move "${mod}$(char_at $shiftnum 8)" 8
bind_move "${mod}$(char_at $shiftnum 9)" 9
# The mapping of Alt + 0 and Alt + Shift + 0 depends on `base-index`.
# It can either refer to workspace number 0 or workspace number 10.
if [ "$(tmux show-option -gv base-index)" = "1" ]; then
bind_switch "${mod}0" 10
bind_move "${mod}$(char_at "$shiftnum" 10)" 10
else
bind_switch "${mod}0" 0
bind_move "${mod}$(char_at "$shiftnum" 10)" 0
fi
# Switch layout with Alt + <mnemonic key>. The mnemonics are `s` and `S` for
# layouts Vim would generate with `:split`, and `v` and `V` for `:vsplit`.
# The remaining mappings based on `z` and `t` should be quite obvious.
bind_layout "${mod}${s}" 'main-horizontal'
bind_layout "${mod}${S}" 'even-vertical'
bind_layout "${mod}${v}" 'main-vertical'
bind_layout "${mod}${V}" 'even-horizontal'
bind_layout "${mod}${t}" 'tiled'
bind_layout "${mod}${z}" 'zoom'
# Refresh the current layout (e.g. after deleting a pane).
if [ -z "$legacy" ]; then
tmux $bind "${mod}${r}" select-layout -E
else
tmux $bind "${mod}${r}" run-shell 'tmux select-layout'\\\; send escape
fi
# Switch pane via Alt + o or move via Alt + Shift + o.
# (This mirrors Tmux `Ctrl-b o` and Emacs `Ctrl-x o`.)
tmux $bind "${mod}${o}" select-pane -t :.+1
tmux $bind "${mod}${O}" swap-pane -D
# Switch to pane via Alt + hjkl.
tmux $bind "${mod}${h}" select-pane -L
tmux $bind "${mod}${j}" select-pane -D
tmux $bind "${mod}${k}" select-pane -U
tmux $bind "${mod}${l}" select-pane -R
# Move a pane via Alt + Shift + hjkl.
if [ -z "$legacy" ]; then
tmux $bind "${mod}${H}" swap-pane -s '{left-of}'
tmux $bind "${mod}${J}" swap-pane -s '{down-of}'
tmux $bind "${mod}${K}" swap-pane -s '{up-of}'
tmux $bind "${mod}${L}" swap-pane -s '{right-of}'
else
tmux $bind "${mod}${H}" run-shell 'old=`tmux display -p "#{pane_index}"`; tmux select-pane -L; tmux swap-pane -t $old'
tmux $bind "${mod}${J}" run-shell 'old=`tmux display -p "#{pane_index}"`; tmux select-pane -D; tmux swap-pane -t $old'
tmux $bind "${mod}${K}" run-shell 'old=`tmux display -p "#{pane_index}"`; tmux select-pane -U; tmux swap-pane -t $old'
tmux $bind "${mod}${L}" run-shell 'old=`tmux display -p "#{pane_index}"`; tmux select-pane -R; tmux swap-pane -t $old'
fi
# Open a terminal with Alt + Enter.
if [ -z "$legacy" ]; then
tmux $bind "${mod}${enter}" \
run-shell 'cwd="`tmux display -p \"#{pane_current_path}\"`"; tmux select-pane -t "bottom-right"; tmux split-pane -c "$cwd"'
else
tmux $bind "${mod}${enter}" \
select-pane -t 'bottom-right' \\\; split-window \\\; run-shell 'tmux select-layout' \\\; send escape
fi
# Name a window with Alt + n.
tmux $bind "${mod}${n}" \
command-prompt -p 'Workspace name:' 'rename-window "%%"'
# Close a window with Alt + Shift + q.
if [ -z "$legacy" ]; then
tmux $bind "${mod}${Q}" \
if-shell \
'[ "$(tmux display-message -p "#{window_panes}")" -gt 1 ]' \
'kill-pane; select-layout; select-layout -E' \
'kill-pane'
else
tmux $bind "${mod}${Q}" \
kill-pane
fi
# Close a connection with Alt + Shift + e.
tmux $bind "${mod}${E}" \
confirm-before -p "Detach from #H:#S? (y/n)" detach-client
# Reload last config file with Alt + Shift + c.
config_file="$(tmux display -p '#{config_files}' | sed 's/^.*,//')"
tmux $bind "${mod}${C}" \
source-file "$config_file" \\\; display "Reloaded $config_file"
# }}}
# Define hooks {{{
if [ -z "$legacy" ]; then
# Autorefresh layout after creating/deleting a pane.
if [ "${enforce:-}" = "none" ]; then
# Never change the layout automatically.
:
elif [ "${enforce:-}" = "size" ]; then
# Enforce pane sizes but not the overall layout.
tmux set-hook -g after-split-window "select-layout -E"
tmux set-hook -g pane-exited "select-layout -E"
else
# Default: Completely enforce the selected layout.
tmux set-hook -g after-split-window "select-layout; select-layout -E"
tmux set-hook -g pane-exited "select-layout; select-layout -E"
fi
# Autoselect layout after creating new window.
if [ -n "${default:-}" ]; then
tmux set-hook -g window-linked "select-layout \"$default\"; select-layout -E"
tmux select-layout "$default"
tmux select-layout -E
fi
fi
# }}}
# Integrate with Vim for transparent navigation {{{
if [ "${navigate:-}" = "on" ]; then
# If `@tilish-navigate` is nonzero, integrate Alt + hjkl with `tmux-navigate`.
tmux set -g '@navigate-left' "-n M-$h"
tmux set -g '@navigate-down' "-n M-$j"
tmux set -g '@navigate-up' "-n M-$k"
tmux set -g '@navigate-right' "-n M-$l"
elif [ "${navigator:-}" = "on" ]; then
# If `@tilish-navigator` is nonzero, integrate Alt + hjkl with `vim-tmux-navigator`.
# This assumes that your Vim/Neovim is setup to use Alt + hjkl bindings as well.
is_vim="ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE '^[^TXZ ]+ +(\\S+\\/)?g?(view|n?vim?x?)(diff)?$'"
tmux $bind "${mod}${h}" if-shell "$is_vim" 'send M-h' 'select-pane -L'
tmux $bind "${mod}${j}" if-shell "$is_vim" 'send M-j' 'select-pane -D'
tmux $bind "${mod}${k}" if-shell "$is_vim" 'send M-k' 'select-pane -U'
tmux $bind "${mod}${l}" if-shell "$is_vim" 'send M-l' 'select-pane -R'
if [ -z "$prefix" ]; then
tmux bind -T copy-mode-vi "M-$h" select-pane -L
tmux bind -T copy-mode-vi "M-$j" select-pane -D
tmux bind -T copy-mode-vi "M-$k" select-pane -U
tmux bind -T copy-mode-vi "M-$l" select-pane -R
fi
fi
# }}}
# Integrate with `fzf` to provide a project launcher {{{
if [ -n "$project" ]; then
tmux $bind "${mod}${p}" run-shell 'this="$(ls --color=yes "'"$project"'" | fzf-tmux -p 100%,100% --color 16 --ansi)" && tmux new-window -c "'"$project"'/$this" -n "$this" || exit 0'
fi
# }}}
# Integrate with `fzf` to approximate `dmenu` {{{
if [ -z "$legacy" ] && [ "${dmenu:-}" = "on" ]; then
if [ -n "$(command -v fzf)" ]; then
# The environment variables of your `default-shell` are used when running `fzf`.
# This solution is about an order of magnitude faster than invoking `compgen`.
# Based on: https://medium.com/njiuko/using-fzf-instead-of-dmenu-2780d184753f
tmux $bind "${mod}${d}" \
run-shell 'app="$(echo "$PATH" | tr ":" "\n" | xargs -I{} -- find {} -maxdepth 1 -mindepth 1 -perm -111 2>/dev/null | sort -u | fzf-tmux -p 100%,100% --color 16)" && tmux new-window "$app" || exit 0'
else
tmux $bind "${mod}${d}" \
display 'To enable this function, install `fzf` and restart `tmux`.'
fi
fi
# }}}