aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--LICENSE28
-rw-r--r--README137
-rw-r--r--tilemux.conf408
3 files changed, 573 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..2f0bd1a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,28 @@
+Copyright (c) 2026, Lena
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/README b/README
new file mode 100644
index 0000000..b4cb829
--- /dev/null
+++ b/README
@@ -0,0 +1,137 @@
+tilemux
+=======
+An i3-like tiling layer for tmux, in a single config file. No plugins, no
+helper scripts, no dependencies. Design target is tmux 3.6.
+
+Windows are workspaces, panes are tiles, and every binding is on Alt so
+no prefix is needed. Ctrl+Space is kept as the tmux prefix so nothing is
+out of reach.
+
+
+Install
+-------
+ cp tilemux.conf ~/.tilemux.conf
+
+Then add an alias to your shell rc:
+
+ alias tilemux='tmux -L tilemux -f ~/.tilemux.conf'
+
+ tilemux new -A -s main start it, or attach if already running
+ tilemux ls list sessions
+ tilemux kill-server stop it
+
+tilemux runs on its own socket, so it has its own server and its own
+sessions and the tmux you already use is untouched. The socket is what
+does the isolating: -f is read only when a server starts, so on its own
+it would be ignored by an already running tmux.
+
+Alt+? lists every key. The rules are commented in tilemux.conf next to
+the bindings that implement them, so this file only shows what they do.
+
+
+The model
+---------
+tmux keeps a layout as a tree but exposes only pane geometry to its
+formats, so the bindings work to one explicit model instead:
+
+ a window is a row of columns, and each column is a stack of panes
+
+Alt+Shift+Left/Right moves a pane between columns. It leaves its own
+column and joins the neighbouring one:
+
+ +----+----+ +----+----+
+ | a | C | | | a |
+ +----+----+ -> | +----+
+ | B | D | | B | C |
+ +----+----+ | +----+
+ | | D |
+ +----+----+
+
+Alt+Shift+Up/Down reorders a pane inside the column it is already in:
+
+ +----+----+ +----+----+
+ | a | C | | B | C |
+ +----+----+ -> +----+----+
+ | B | D | | a | D |
+ +----+----+ +----+----+
+
+At the edge of the window there is no neighbour to move to, so the pane
+leaves its column and becomes a column of its own, or a full-width row
+going up or down. This is how a layout is restructured without presets:
+
+ +----+----+ +--+--+----+
+ | A | C | |A | | |
+ +----+----+ -> +--+ C| d |
+ | B | d | |B | | |
+ +----+----+ +--+--+----+
+
+Alt+m and a direction is the way back in. Moving pushes panes apart, and
+a pane that is a whole column swaps with its neighbours instead of
+joining them, so merging needs a key of its own:
+
+ +---+---+---+ +---+-------+
+ | | | | | | B |
+ | A | B | c | -> | A +-------+
+ | | | | | | c |
+ +---+---+---+ +---+-------+
+
+
+Debugging
+---------
+Reload, then look at what a key runs and what the rules are reading:
+
+ tilemux source-file ~/.tilemux.conf
+ tilemux list-keys -T root | grep 'M-L'
+ tilemux display -p '#{window_layout}'
+ tilemux list-panes -F '#{pane_index} #{pane_left},#{pane_top}'
+
+Watch for #{>:a,b}: it compares strings. Numbers need #{e|>:a,b}.
+
+To try a binding without disturbing the tmux you work in, run a second
+server on its own socket and attach to it from a pane of the first.
+send-keys writes to that pane, so the attached client reads the keys and
+the binding fires:
+
+ tmux -L test -f ./tilemux.conf new-session -d -s w -x 200 -y 48
+ tmux -L ctl -f /dev/null new-session -d -x 200 -y 49 \
+ 'tmux -L test attach -t w'
+ tmux -L test split-window -h
+ tmux -L test select-pane -t 1
+ tmux -L ctl send-keys -t 0 M-L
+ tmux -L test list-panes -F '#{pane_index} #{pane_left} #{pane_active}'
+ tmux -L test kill-server
+
+Killing the test server ends the attach, and the ctl server with it.
+
+
+Caveats
+-------
+A 2x2 built as columns of rows and one built as rows of columns look
+identical on screen. Geometry cannot tell them apart, so both behave as
+columns of rows.
+
+A pane that spans the window swaps with a neighbour instead of pushing
+past it, and tmux picks which neighbour by geometry, so a full-height
+column can trade places with one pane of the stack beside it. Alt+m
+puts it back.
+
+A pane leaving a stack does not make the rest rebalance; the neighbour
+absorbs the space, as it does when a pane is killed. Alt+r then = evens
+a container out.
+
+Copy mode has its own key table. With the default emacs mode-keys it
+takes seventeen of these keys for itself: Alt+1 to Alt+9, Alt+Up,
+Alt+Down, Alt+b, Alt+f, Alt+l, Alt+m, Alt+r and Alt+v. Those do not
+reach tilemux while a pane is in copy mode, and every other key falls
+through and works. setw -g mode-keys vi avoids the clash entirely.
+
+Alt+? needs a terminal of at least 76x23. Below that tmux refuses the
+popup and says so in the status line; there is no smaller help.
+
+Alt+Shift with a number assumes the shifted symbols of a US layout
+(!@#$%^&*()).
+
+
+License
+-------
+BSD 3-Clause. See LICENSE.
diff --git a/tilemux.conf b/tilemux.conf
new file mode 100644
index 0000000..5969013
--- /dev/null
+++ b/tilemux.conf
@@ -0,0 +1,408 @@
+# ~/.tilemux.conf - i3-like tiling for tmux
+# Tested design target: tmux 3.6
+#
+# Main modifier: Alt (tmux calls it Meta, written M-).
+# These bindings do NOT require the normal tmux prefix.
+#
+# Layout model: a window is a row of columns, each column a stack of panes.
+# Alt+Shift+left/right move the pane to the neighbouring column
+# Alt+Shift+up/down move the pane within its own column
+# at the window edge the pane leaves its column and becomes a
+# full-width row or full-height column of its own
+# Alt+m then direction merge the pane into the neighbour, the way back
+#
+# Quick help: Alt+?
+
+##### General ###############################################################
+
+# Treat tmux windows like numbered workspaces.
+set -g base-index 1
+setw -g pane-base-index 1
+set -g renumber-windows off
+
+# Put status at the top.
+set -g status-position top
+
+# Alt must not be read as Escape then a key. This is the default on 3.6;
+# setting it explicitly covers older tmux.
+set -sg escape-time 10
+
+# Keep a normal tmux prefix as an escape hatch.
+unbind C-b
+set -g prefix C-Space
+bind C-Space send-prefix
+
+set -g history-limit 50000
+set -g focus-events on
+
+
+##### Help: Alt+? ###########################################################
+# Shift+/ produces ?, so Alt+? is M-? in tmux notation.
+
+bind -n 'M-?' display-popup -E -w 76 -h 23 -T ' tilemux - Enter to close ' 'printf "%s\n" \
+ " WORKSPACES" \
+ " Alt+1..9,0 select/create workspace (0 is workspace 10)" \
+ " Alt+Shift+1..9,0 move pane there; a lone pane takes its window" \
+ " Ctrl+Alt+Left/Right previous/next workspace" \
+ " Alt+Tab, Alt+backtick last workspace" \
+ " Alt+n rename workspace" \
+ " FOCUS" \
+ " Alt+h/j/k/l or Arrow focus left/down/up/right" \
+ " Alt+o last pane" \
+ " MOVE PANE" \
+ " Alt+Shift+h/j/k/l/Arrow left/right to the next column, up/down in it" \
+ " at the window edge: a full row or column" \
+ " Alt+m then a direction merge into the neighbour, the way back" \
+ " PANES" \
+ " Alt+Enter new pane, split along the longer side" \
+ " Alt+b / Alt+v split left|right / top|bottom" \
+ " Alt+f / Alt+Shift+q fullscreen/zoom / kill pane" \
+ " Alt+e cycle tmux preset layouts" \
+ " Alt+r resize mode: hjkl/arrows, = evens, Enter/Esc" \
+ " Ctrl+Space normal tmux prefix (Alt+? shows this list)"; read _'
+
+
+##### Focus panes: Alt+hjkl AND Alt+arrows ##################################
+
+bind -n M-h select-pane -L
+bind -n M-j select-pane -D
+bind -n M-k select-pane -U
+bind -n M-l select-pane -R
+
+bind -n M-Left select-pane -L
+bind -n M-Down select-pane -D
+bind -n M-Up select-pane -U
+bind -n M-Right select-pane -R
+
+# Back to the pane focused before this one.
+bind -n M-o last-pane
+
+
+##### Move panes: Alt+Shift+hjkl AND Alt+Shift+arrows #######################
+#
+# tmux exposes pane geometry to formats but not its layout tree, so the
+# rules are expressed in geometry:
+#
+# left/right pane is as tall as the window -> it is a whole column, so
+# swap it with the neighbour
+# otherwise -> leave this column and join
+# the neighbouring one
+# nothing in that direction -> become a full-height column
+# at that edge of the window
+#
+# up/down a pane in that direction -> swap with it
+# nothing in that direction -> become a full-width row at
+# that edge of the window
+#
+# A pane that already spans the window along the relevant axis does nothing.
+# That is what stops repeated presses from nesting forever.
+#
+# Notes:
+# - {left-of} and friends WRAP AROUND the window, so every use of them is
+# guarded by the matching pane_at_* flag.
+# - tmux takes the source of a join or swap from the marked pane whenever
+# one is set, whatever -s says, so the mark is cleared first, even when
+# the rules below then decide to do nothing. Marks belong to the join
+# and swap workflow these bindings replace.
+# - the pane joins above the neighbour, or below it when moving out of the
+# bottom row, so it keeps roughly the row it came from.
+# - select-layout -E gives every pane in the destination column an equal
+# share, the way i3 does when a window joins a container.
+# - the logic lives in command aliases because tmux cannot bind two keys
+# to one command list any other way. Aliases take no arguments.
+# - a zoomed pane covers the window, so every rule would read as "already
+# spans everything" and do nothing. Moving unzooms first, which is what
+# select-pane does for focus anyway.
+
+set -s command-alias[100] "tile-left=select-pane -M ; \
+if -F '#{window_zoomed_flag}' { resize-pane -Z } ; \
+if -F '#{pane_at_left}' { \
+ if -F '#{&&:#{!=:#{window_panes},1},#{!=:#{pane_height},#{window_height}}}' { \
+ join-pane -h -f -b -t '{next}' ; select-layout -E \
+ } \
+} { \
+ if -F '#{==:#{pane_height},#{window_height}}' { \
+ swap-pane -d -t '{left-of}' \
+ } { \
+ if -F '#{pane_at_bottom}' { \
+ join-pane -v -t '{left-of}' ; select-layout -E \
+ } { \
+ join-pane -v -b -t '{left-of}' ; select-layout -E \
+ } \
+ } \
+}"
+
+set -s command-alias[101] "tile-right=select-pane -M ; \
+if -F '#{window_zoomed_flag}' { resize-pane -Z } ; \
+if -F '#{pane_at_right}' { \
+ if -F '#{&&:#{!=:#{window_panes},1},#{!=:#{pane_height},#{window_height}}}' { \
+ join-pane -h -f -t '{next}' ; select-layout -E \
+ } \
+} { \
+ if -F '#{==:#{pane_height},#{window_height}}' { \
+ swap-pane -d -t '{right-of}' \
+ } { \
+ if -F '#{pane_at_bottom}' { \
+ join-pane -v -t '{right-of}' ; select-layout -E \
+ } { \
+ join-pane -v -b -t '{right-of}' ; select-layout -E \
+ } \
+ } \
+}"
+
+set -s command-alias[102] "tile-up=select-pane -M ; \
+if -F '#{window_zoomed_flag}' { resize-pane -Z } ; \
+if -F '#{pane_at_top}' { \
+ if -F '#{&&:#{!=:#{window_panes},1},#{!=:#{pane_width},#{window_width}}}' { \
+ join-pane -v -f -b -t '{next}' ; select-layout -E \
+ } \
+} { \
+ swap-pane -d -t '{up-of}' \
+}"
+
+set -s command-alias[103] "tile-down=select-pane -M ; \
+if -F '#{window_zoomed_flag}' { resize-pane -Z } ; \
+if -F '#{pane_at_bottom}' { \
+ if -F '#{&&:#{!=:#{window_panes},1},#{!=:#{pane_width},#{window_width}}}' { \
+ join-pane -v -f -t '{next}' ; select-layout -E \
+ } \
+} { \
+ swap-pane -d -t '{down-of}' \
+}"
+
+bind -n M-H tile-left
+bind -n M-J tile-down
+bind -n M-K tile-up
+bind -n M-L tile-right
+
+bind -n M-S-Left tile-left
+bind -n M-S-Down tile-down
+bind -n M-S-Up tile-up
+bind -n M-S-Right tile-right
+
+
+##### Merge into the neighbour: Alt+m, then a direction #####################
+#
+# Alt+Shift+direction pushes a pane out of its column, and once a pane is a
+# whole column of its own it swaps with its neighbours instead of joining
+# them. This is the way back in: merge always joins the neighbouring pane,
+# whatever the shape of either of them.
+#
+# Alt+m h/l stack into the column to the left/right
+# Alt+m j/k sit beside the pane below/above, turning a stack into a row
+#
+# Nothing happens at the window edge, because there is no neighbour there.
+# The key table reverts to root after one key, so this is a single chord,
+# not a mode that has to be left.
+
+set -s command-alias[104] "tile-merge-left=select-pane -M ; \
+if -F '#{window_zoomed_flag}' { resize-pane -Z } ; \
+if -F '#{==:#{pane_at_left},0}' { \
+ if -F '#{pane_at_bottom}' { \
+ join-pane -v -t '{left-of}' ; select-layout -E \
+ } { \
+ join-pane -v -b -t '{left-of}' ; select-layout -E \
+ } \
+}"
+
+set -s command-alias[105] "tile-merge-right=select-pane -M ; \
+if -F '#{window_zoomed_flag}' { resize-pane -Z } ; \
+if -F '#{==:#{pane_at_right},0}' { \
+ if -F '#{pane_at_bottom}' { \
+ join-pane -v -t '{right-of}' ; select-layout -E \
+ } { \
+ join-pane -v -b -t '{right-of}' ; select-layout -E \
+ } \
+}"
+
+set -s command-alias[106] "tile-merge-up=select-pane -M ; \
+if -F '#{window_zoomed_flag}' { resize-pane -Z } ; \
+if -F '#{==:#{pane_at_top},0}' { \
+ if -F '#{pane_at_right}' { \
+ join-pane -h -t '{up-of}' ; select-layout -E \
+ } { \
+ join-pane -h -b -t '{up-of}' ; select-layout -E \
+ } \
+}"
+
+set -s command-alias[107] "tile-merge-down=select-pane -M ; \
+if -F '#{window_zoomed_flag}' { resize-pane -Z } ; \
+if -F '#{==:#{pane_at_bottom},0}' { \
+ if -F '#{pane_at_right}' { \
+ join-pane -h -t '{down-of}' ; select-layout -E \
+ } { \
+ join-pane -h -b -t '{down-of}' ; select-layout -E \
+ } \
+}"
+
+bind -n M-m switch-client -T merge
+
+bind -T merge h tile-merge-left
+bind -T merge j tile-merge-down
+bind -T merge k tile-merge-up
+bind -T merge l tile-merge-right
+
+bind -T merge Left tile-merge-left
+bind -T merge Down tile-merge-down
+bind -T merge Up tile-merge-up
+bind -T merge Right tile-merge-right
+
+bind -T merge Enter switch-client -T root
+bind -T merge Escape switch-client -T root
+
+
+##### Create panes / layout #################################################
+
+# Alt+Enter: new pane, split along the longer side of the current pane.
+# Terminal cells are about twice as tall as they are wide, hence the *2.
+bind -n M-Enter if -F '#{e|>:#{pane_width},#{e|*:#{pane_height},2}}' {
+ split-window -h -c '#{pane_current_path}'
+} {
+ split-window -v -c '#{pane_current_path}'
+}
+
+# Explicit split orientation.
+bind -n M-b split-window -h -c '#{pane_current_path}'
+bind -n M-v split-window -v -c '#{pane_current_path}'
+
+bind -n M-e next-layout
+bind -n M-f resize-pane -Z
+
+# Shift+q arrives as Q.
+bind -n M-Q kill-pane
+
+
+##### Resize mode: Alt+r, then hjkl OR arrows ###############################
+#
+# The mode stays active until Enter or Escape. Any other key leaves it too,
+# because unbound keys fall back to the root table. status-right names the
+# key table whenever it is not root, so the mode is visible.
+
+bind -n M-r switch-client -T resize
+
+bind -T resize h resize-pane -L 5 \; switch-client -T resize
+bind -T resize j resize-pane -D 2 \; switch-client -T resize
+bind -T resize k resize-pane -U 2 \; switch-client -T resize
+bind -T resize l resize-pane -R 5 \; switch-client -T resize
+
+bind -T resize Left resize-pane -L 5 \; switch-client -T resize
+bind -T resize Down resize-pane -D 2 \; switch-client -T resize
+bind -T resize Up resize-pane -U 2 \; switch-client -T resize
+bind -T resize Right resize-pane -R 5 \; switch-client -T resize
+
+# Give the panes around this one equal shares, without leaving the mode.
+bind -T resize = select-layout -E \; switch-client -T resize
+
+bind -T resize Enter switch-client -T root
+bind -T resize Escape switch-client -T root
+
+
+##### Workspaces ############################################################
+#
+# ":N" means window N in the current session. Alt+0 is workspace 10, so the
+# workspaces read 1..10 in order. list-panes is only a probe for "does this
+# window exist", so its output and its error are both discarded. It runs
+# the tmux binary, so tmux has to be on PATH in the server environment;
+# without it, selecting a workspace that exists fails with "index in use".
+# Command aliases cannot help here: tmux does not expand formats in -t.
+
+bind -n M-1 if-shell 'tmux list-panes -t :1 >/dev/null 2>&1' { select-window -t :1 } { new-window -t :1 -c '#{pane_current_path}' }
+bind -n M-2 if-shell 'tmux list-panes -t :2 >/dev/null 2>&1' { select-window -t :2 } { new-window -t :2 -c '#{pane_current_path}' }
+bind -n M-3 if-shell 'tmux list-panes -t :3 >/dev/null 2>&1' { select-window -t :3 } { new-window -t :3 -c '#{pane_current_path}' }
+bind -n M-4 if-shell 'tmux list-panes -t :4 >/dev/null 2>&1' { select-window -t :4 } { new-window -t :4 -c '#{pane_current_path}' }
+bind -n M-5 if-shell 'tmux list-panes -t :5 >/dev/null 2>&1' { select-window -t :5 } { new-window -t :5 -c '#{pane_current_path}' }
+bind -n M-6 if-shell 'tmux list-panes -t :6 >/dev/null 2>&1' { select-window -t :6 } { new-window -t :6 -c '#{pane_current_path}' }
+bind -n M-7 if-shell 'tmux list-panes -t :7 >/dev/null 2>&1' { select-window -t :7 } { new-window -t :7 -c '#{pane_current_path}' }
+bind -n M-8 if-shell 'tmux list-panes -t :8 >/dev/null 2>&1' { select-window -t :8 } { new-window -t :8 -c '#{pane_current_path}' }
+bind -n M-9 if-shell 'tmux list-panes -t :9 >/dev/null 2>&1' { select-window -t :9 } { new-window -t :9 -c '#{pane_current_path}' }
+bind -n M-0 if-shell 'tmux list-panes -t :10 >/dev/null 2>&1' { select-window -t :10 } { new-window -t :10 -c '#{pane_current_path}' }
+
+bind -n C-M-Left previous-window
+bind -n C-M-Right next-window
+
+# Back to the workspace used before this one. Alt+backtick is there because
+# desktops that are not i3 tend to swallow Alt+Tab before the terminal sees
+# it.
+bind -n M-Tab last-window
+bind -n 'M-`' last-window
+
+# %%% substitutes the answer with " \ $ ; ~ escaped, so a name cannot end
+# the command and start another one, and -- keeps a leading dash a name.
+# Two costs: a backslash in a name arrives doubled, and tmux expands #{}
+# and #() in the name itself, which no quoting here can prevent.
+bind -n M-n command-prompt -p 'rename workspace:' 'rename-window -- "%%%"'
+
+
+##### Alt+Shift+number = move pane to workspace #############################
+#
+# Shifted number encoding:
+# 1 ! 2 @ 3 # 4 $ 5 % 6 ^ 7 & 8 * 9 ( 0 )
+#
+# Behaviour:
+# - if the destination exists: join the pane there with -d, so this
+# workspace stays selected and focus falls to a pane that remains here;
+# - if it does not exist and this window has other panes: break the pane
+# into window N with -d, again staying here;
+# - if this is the only pane in the window, the whole window has to move,
+# because no pane would be left here to stay focused on. That does
+# follow the window to workspace N.
+
+bind -n 'M-!' if -F '#{!=:#{window_index},1}' {
+ if-shell 'tmux list-panes -t :1 >/dev/null 2>&1' { select-pane -M ; join-pane -d -h -t :1 } {
+ if -F '#{==:#{window_panes},1}' { move-window -t :1 } { break-pane -d -t :1 }
+ }
+}
+bind -n 'M-@' if -F '#{!=:#{window_index},2}' {
+ if-shell 'tmux list-panes -t :2 >/dev/null 2>&1' { select-pane -M ; join-pane -d -h -t :2 } {
+ if -F '#{==:#{window_panes},1}' { move-window -t :2 } { break-pane -d -t :2 }
+ }
+}
+bind -n 'M-#' if -F '#{!=:#{window_index},3}' {
+ if-shell 'tmux list-panes -t :3 >/dev/null 2>&1' { select-pane -M ; join-pane -d -h -t :3 } {
+ if -F '#{==:#{window_panes},1}' { move-window -t :3 } { break-pane -d -t :3 }
+ }
+}
+bind -n 'M-$' if -F '#{!=:#{window_index},4}' {
+ if-shell 'tmux list-panes -t :4 >/dev/null 2>&1' { select-pane -M ; join-pane -d -h -t :4 } {
+ if -F '#{==:#{window_panes},1}' { move-window -t :4 } { break-pane -d -t :4 }
+ }
+}
+bind -n 'M-%' if -F '#{!=:#{window_index},5}' {
+ if-shell 'tmux list-panes -t :5 >/dev/null 2>&1' { select-pane -M ; join-pane -d -h -t :5 } {
+ if -F '#{==:#{window_panes},1}' { move-window -t :5 } { break-pane -d -t :5 }
+ }
+}
+bind -n 'M-^' if -F '#{!=:#{window_index},6}' {
+ if-shell 'tmux list-panes -t :6 >/dev/null 2>&1' { select-pane -M ; join-pane -d -h -t :6 } {
+ if -F '#{==:#{window_panes},1}' { move-window -t :6 } { break-pane -d -t :6 }
+ }
+}
+bind -n 'M-&' if -F '#{!=:#{window_index},7}' {
+ if-shell 'tmux list-panes -t :7 >/dev/null 2>&1' { select-pane -M ; join-pane -d -h -t :7 } {
+ if -F '#{==:#{window_panes},1}' { move-window -t :7 } { break-pane -d -t :7 }
+ }
+}
+bind -n 'M-*' if -F '#{!=:#{window_index},8}' {
+ if-shell 'tmux list-panes -t :8 >/dev/null 2>&1' { select-pane -M ; join-pane -d -h -t :8 } {
+ if -F '#{==:#{window_panes},1}' { move-window -t :8 } { break-pane -d -t :8 }
+ }
+}
+bind -n 'M-(' if -F '#{!=:#{window_index},9}' {
+ if-shell 'tmux list-panes -t :9 >/dev/null 2>&1' { select-pane -M ; join-pane -d -h -t :9 } {
+ if -F '#{==:#{window_panes},1}' { move-window -t :9 } { break-pane -d -t :9 }
+ }
+}
+bind -n 'M-)' if -F '#{!=:#{window_index},10}' {
+ if-shell 'tmux list-panes -t :10 >/dev/null 2>&1' { select-pane -M ; join-pane -d -h -t :10 } {
+ if -F '#{==:#{window_panes},1}' { move-window -t :10 } { break-pane -d -t :10 }
+ }
+}
+
+
+##### Status bar ############################################################
+# %Y etc are expanded by tmux itself, so the status line forks nothing.
+
+set -g status-left '[#S] '
+set -g status-right '#{?#{==:#{client_key_table},root},,[#{client_key_table}] }%Y-%m-%d %H:%M'
+set -g window-status-format ' #I:#W '
+set -g window-status-current-format '[#I:#W#{?window_zoomed_flag, Z,}]'