mirror of
https://github.com/tmux/tmux.git
synced 2026-07-03 10:12:31 +00:00
Update select-layout and list-windows to work with new layouts,
floating panes, and use a more modern serialization format.
## Serialization
layout-custom.c now emits a canonical current format with:
- Pane syntax: %pane-id,z-index:geometry[:flags]
- Semicolons between child cells.
- X-style geometry: WxH+X+Y
- Every pane represented inside layout_root, including floating panes.
- No legacy <...> floating-pane wrapper.
- Z-index serialized for tiled and floating panes.
- Flags:
- f: floating
- h: hidden
- z: zoomed
- Hidden panes use their saved restoration geometry.
- Single-pane windows serialize as a pane root cell.
window_layout and list-windows continue emitting a four-digit checksum.
## Geometry
Current layouts accept:
- +N: absolute positive offset
- ++N: equivalent to +N
- +-N: absolute negative offset
- -N: right/bottom-relative offset for floating panes
- Omitted offsets: +0+0
- One offset: second defaults to +0
Relative offsets are resolved immediately. Output always contains canonical absolute
coordinates.
Widths and heights are restricted to 1..PANE_MAXIMUM; offsets and resolved offsets are
restricted to -PANE_MAXIMUM..PANE_MAXIMUM.
## Checksums and whitespace
- The outer checksum is optional on input for current and legacy layouts.
- If supplied, it must be correct.
- Output still includes it.
- Checksums cover all non-whitespace layout text.
- Whitespace and newlines may be inserted between tokens.
- Optional nested checksums are accepted and validated but not emitted.
## Legacy compatibility
Legacy comma-separated layouts remain accepted:
WxH,x,y{cell,cell,...}
WxH,x,y,pane-id
Additional compatibility behavior includes:
- The reported e6db,113x28,... layout works and resizes existing panes correctly.
- Older inconsistent root dimensions are corrected safely.
- Legacy layouts with surplus cells continue pruning cells until the target pane count
matches.
- Legacy panes remain assigned in tree order.
- Legacy input is re-emitted in the current canonical format.
Mixed legacy/current syntax is rejected.
## Pane matching and counts
For current layouts:
- Pane IDs must be unique.
- With matching pane counts:
- An exact ID set maps panes by identity.
- A completely disjoint ID set maps panes in tree order.
- A partial ID match is rejected as ambiguous.
- If the target has fewer panes:
- Cells are removed from the end of the tree.
- Remaining z-indexes are compacted while preserving order.
- Existing panes are assigned in tree order.
- No panes are killed or created.
- A target with more panes than layout cells is rejected.
Layouts are therefore snapshots that rearrange existing panes and windows, not complete
session-restoration data.
## Validation and safety
Parsing now constructs and validates a temporary tree before replacing the active layout:
- Maximum input length: 8192 bytes.
- Maximum nesting depth: 64.
- Checked signed and unsigned numeric parsing.
- Dimension and offset bounds.
- Container size consistency.
- Exact trailing-input checks.
- Unique, contiguous z-indexes starting at zero.
- Floating panes must precede tiled panes in z-order.
- Unknown and duplicate flags are rejected.
- Hidden and zoomed flags cannot be combined.
- Only one pane may be zoomed.
- Relative positioning is accepted only for floating panes.
- Overflow-safe size aggregation.
Invalid single-window layouts are validated before the window is unzoomed. Multi-window
layouts are all validated before any window is changed.
## Multi-window layouts
cmd-select-layout.c accepts:
@window-id:layout[@window-id:layout...]
Records may be adjacent or separated by whitespace/newlines. Unknown and duplicate window IDs
are rejected.
This allows direct reuse of:
tmux list-windows -F '#{window_id}:#{window_layout}'
## Supporting runtime changes
- tmux.h adds parsed pane IDs, z-indexes, hidden/zoomed/relative cell flags, and
layout_validate.
- layout.c initializes parsed metadata safely.
- window.c treats hidden cells, including saved cells, as non-visible.
## Documentation
tmux.1 now documents:
- Current and legacy grammars.
- Geometry and flags.
- Z-index and pane-ID rules.
- Optional checksums.
- Multi-window wrappers.
- Pane-count pruning.
- Snapshot versus session-restoration semantics.
The authoritative description is under select-layout; list-windows and list-panes reference
it.
## Tests
Added regress/layout-custom.sh covering serialization, compatibility, validation, geometry,
IDs, pruning, flags, zoom, checksums, whitespace, and multi-window application.
Updated regress/control-client-sanity.sh for canonical output.
Verified:
- Debug build
- Layout regression
- Floating-pane geometry regression
- Control-client regression
- Man-page rendering
- git diff --check
This commit is contained in:
@@ -18,7 +18,9 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "tmux.h"
|
||||
|
||||
@@ -28,6 +30,13 @@
|
||||
|
||||
static enum cmd_retval cmd_select_layout_exec(struct cmd *,
|
||||
struct cmdq_item *);
|
||||
static enum cmd_retval cmd_select_layout_exec_multiple(struct cmdq_item *,
|
||||
const char *);
|
||||
|
||||
struct cmd_select_layout_record {
|
||||
struct window *w;
|
||||
char *layout;
|
||||
};
|
||||
|
||||
const struct cmd_entry cmd_select_layout_entry = {
|
||||
.name = "select-layout",
|
||||
@@ -68,6 +77,120 @@ const struct cmd_entry cmd_previous_layout_entry = {
|
||||
.exec = cmd_select_layout_exec
|
||||
};
|
||||
|
||||
static void
|
||||
cmd_select_layout_free_records(struct cmd_select_layout_record *records,
|
||||
u_int nrecords)
|
||||
{
|
||||
u_int i;
|
||||
|
||||
for (i = 0; i < nrecords; i++)
|
||||
free(records[i].layout);
|
||||
free(records);
|
||||
}
|
||||
|
||||
/* Apply a list of @window-id:layout records. */
|
||||
static enum cmd_retval
|
||||
cmd_select_layout_exec_multiple(struct cmdq_item *item, const char *input)
|
||||
{
|
||||
struct cmd_select_layout_record *records = NULL;
|
||||
struct window *w;
|
||||
const char *ptr = input, *idstart, *idend;
|
||||
const char *layoutstart, *layoutend;
|
||||
char *id, *cause, *oldlayout;
|
||||
u_int i, nrecords = 0;
|
||||
|
||||
for (;;) {
|
||||
while (*ptr != '\0' && isspace((u_char)*ptr))
|
||||
ptr++;
|
||||
if (*ptr == '\0')
|
||||
break;
|
||||
if (*ptr != '@')
|
||||
goto invalid;
|
||||
|
||||
idstart = ptr++;
|
||||
if (!isdigit((u_char)*ptr))
|
||||
goto invalid;
|
||||
while (isdigit((u_char)*ptr))
|
||||
ptr++;
|
||||
idend = ptr;
|
||||
while (*ptr != '\0' && isspace((u_char)*ptr))
|
||||
ptr++;
|
||||
if (*ptr++ != ':')
|
||||
goto invalid;
|
||||
|
||||
id = xstrndup(idstart, idend - idstart);
|
||||
w = window_find_by_id_str(id);
|
||||
if (w == NULL) {
|
||||
cmdq_error(item, "unknown window: %s", id);
|
||||
free(id);
|
||||
goto fail;
|
||||
}
|
||||
free(id);
|
||||
for (i = 0; i < nrecords; i++) {
|
||||
if (records[i].w == w) {
|
||||
cmdq_error(item, "duplicate window: @%u", w->id);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
layoutstart = ptr;
|
||||
while (*ptr != '\0' && *ptr != '@')
|
||||
ptr++;
|
||||
layoutend = ptr;
|
||||
while (layoutend != layoutstart &&
|
||||
isspace((u_char)layoutend[-1]))
|
||||
layoutend--;
|
||||
if (layoutend == layoutstart)
|
||||
goto invalid;
|
||||
|
||||
records = xreallocarray(records, nrecords + 1,
|
||||
sizeof *records);
|
||||
records[nrecords].w = w;
|
||||
records[nrecords].layout = xstrndup(layoutstart,
|
||||
layoutend - layoutstart);
|
||||
nrecords++;
|
||||
}
|
||||
if (nrecords == 0)
|
||||
goto invalid;
|
||||
|
||||
/* Validate every record before changing any window. */
|
||||
for (i = 0; i < nrecords; i++) {
|
||||
if (layout_validate(records[i].w, records[i].layout,
|
||||
&cause) != 0) {
|
||||
cmdq_error(item, "@%u: %s", records[i].w->id, cause);
|
||||
free(cause);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < nrecords; i++) {
|
||||
w = records[i].w;
|
||||
server_unzoom_window(w);
|
||||
oldlayout = w->old_layout;
|
||||
w->old_layout = layout_dump(w, w->layout_root);
|
||||
if (layout_parse(w, records[i].layout, &cause) != 0) {
|
||||
cmdq_error(item, "@%u: %s", w->id, cause);
|
||||
free(cause);
|
||||
free(w->old_layout);
|
||||
w->old_layout = oldlayout;
|
||||
goto fail;
|
||||
}
|
||||
free(oldlayout);
|
||||
recalculate_sizes();
|
||||
server_redraw_window(w);
|
||||
notify_window("window-layout-changed", w);
|
||||
}
|
||||
|
||||
cmd_select_layout_free_records(records, nrecords);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
invalid:
|
||||
cmdq_error(item, "invalid multiple-window layout");
|
||||
fail:
|
||||
cmd_select_layout_free_records(records, nrecords);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item)
|
||||
{
|
||||
@@ -79,6 +202,24 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item)
|
||||
const char *layoutname;
|
||||
char *oldlayout, *cause;
|
||||
int next, previous, layout;
|
||||
const char *ptr;
|
||||
|
||||
if (cmd_get_entry(self) == &cmd_select_layout_entry &&
|
||||
args_count(args) != 0 && !args_has(args, 'E') &&
|
||||
!args_has(args, 'n') && !args_has(args, 'o') &&
|
||||
!args_has(args, 'p')) {
|
||||
ptr = args_string(args, 0);
|
||||
while (*ptr != '\0' && isspace((u_char)*ptr))
|
||||
ptr++;
|
||||
if (*ptr == '@')
|
||||
return (cmd_select_layout_exec_multiple(item, ptr));
|
||||
if (layout_set_lookup(ptr) == -1 &&
|
||||
layout_validate(w, ptr, &cause) != 0) {
|
||||
cmdq_error(item, "%s: %s", cause, ptr);
|
||||
free(cause);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
server_unzoom_window(w);
|
||||
|
||||
|
||||
1067
layout-custom.c
1067
layout-custom.c
File diff suppressed because it is too large
Load Diff
2
layout.c
2
layout.c
@@ -64,6 +64,8 @@ layout_create_cell(struct layout_cell *lcparent)
|
||||
lc = xmalloc(sizeof *lc);
|
||||
lc->type = LAYOUT_WINDOWPANE;
|
||||
lc->flags = 0;
|
||||
lc->z_index = UINT_MAX;
|
||||
lc->pane_id = UINT_MAX;
|
||||
lc->parent = lcparent;
|
||||
|
||||
TAILQ_INIT(&lc->cells);
|
||||
|
||||
@@ -32,10 +32,10 @@ sleep 1
|
||||
$TMUX has || exit 1
|
||||
$TMUX lsp -aF '#{pane_id} #{window_layout}' >$TMP || exit 1
|
||||
cat <<EOF|cmp -s $TMP - || exit 1
|
||||
%0 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
|
||||
%3 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
|
||||
%2 dcbd,200x200,0,0[200x100,0,0,2,200x99,0,101,4]
|
||||
%4 dcbd,200x200,0,0[200x100,0,0,2,200x99,0,101,4]
|
||||
%0 3cf7,200x200+0+0[%0,0:200x50+0+0;%3,1:200x149+0+51]
|
||||
%3 3cf7,200x200+0+0[%0,0:200x50+0+0;%3,1:200x149+0+51]
|
||||
%2 e179,200x200+0+0[%2,0:200x100+0+0;%4,1:200x99+0+101]
|
||||
%4 e179,200x200+0+0[%2,0:200x100+0+0;%4,1:200x99+0+101]
|
||||
EOF
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
|
||||
230
regress/layout-custom.sh
Executable file
230
regress/layout-custom.sh
Executable file
@@ -0,0 +1,230 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Test legacy and extended custom layout strings.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Llayout-custom-test -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 1
|
||||
}
|
||||
|
||||
must_equal()
|
||||
{
|
||||
[ "$1" = "$2" ] || fail "got '$1', expected '$2'"
|
||||
}
|
||||
|
||||
must_fail()
|
||||
{
|
||||
"$@" >/dev/null 2>&1 && fail "unexpected success: $*"
|
||||
return 0
|
||||
}
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX new-session -d -x 80 -y 24 || exit 1
|
||||
$TMUX split-window -h || fail "split-window failed"
|
||||
|
||||
# Legacy comma-separated layouts are accepted and emitted in the new form.
|
||||
legacy='89f5,80x24,0,0{39x24,0,0,0,40x24,40,0,1}'
|
||||
$TMUX select-layout "$legacy" || fail "legacy layout was rejected"
|
||||
layout=$($TMUX display-message -p '#{window_layout}')
|
||||
must_equal "$layout" \
|
||||
'93fc,80x24+0+0{%0,0:39x24+0+0;%1,1:40x24+40+0}'
|
||||
|
||||
# Both current and legacy layouts may omit the outer checksum.
|
||||
$TMUX select-layout '80x24+0+0{%0,0:39x24+0+0;%1,1:40x24+40+0}' || \
|
||||
fail "checksumless current layout was rejected"
|
||||
must_equal "$($TMUX display-message -p '#{window_layout}')" "$layout"
|
||||
$TMUX select-layout '80x24,0,0{39x24,0,0,0,40x24,40,0,1}' || \
|
||||
fail "checksumless legacy layout was rejected"
|
||||
must_equal "$($TMUX display-message -p '#{window_layout}')" "$layout"
|
||||
must_fail $TMUX select-layout \
|
||||
'0000,80x24+0+0{%0,0:39x24+0+0;%1,1:40x24+40+0}'
|
||||
|
||||
# Whitespace and newlines between tokens do not affect the checksum.
|
||||
indented='93fc,
|
||||
80x24 +0 +0 {
|
||||
%0, 0: 39x24 +0 +0;
|
||||
%1, 1: 40x24 +40 +0
|
||||
}'
|
||||
$TMUX select-layout "$indented" || fail "indented layout was rejected"
|
||||
must_equal "$($TMUX display-message -p '#{window_layout}')" "$layout"
|
||||
|
||||
# Reported legacy compatibility case: resize two existing panes to 113x28.
|
||||
reported='e6db,113x28,0,0{56x28,0,0,0,56x28,57,0,1}'
|
||||
$TMUX select-layout "$reported" || fail "reported legacy layout was rejected"
|
||||
must_equal "$($TMUX display-message -p '#{window_width}x#{window_height}')" \
|
||||
'113x28'
|
||||
must_equal "$($TMUX list-panes -F '#{pane_width}x#{pane_height},#{pane_left},#{pane_top}')" \
|
||||
'56x28,0,0
|
||||
56x28,57,0'
|
||||
|
||||
# Return to the smaller layout for the remaining parser tests.
|
||||
$TMUX select-layout "$legacy" || fail "legacy layout could not be reapplied"
|
||||
layout=$($TMUX display-message -p '#{window_layout}')
|
||||
|
||||
# A checksum may be attached to each nested cell but is not emitted.
|
||||
nested='73a1,80x24+0+0{6806,%0,0:39x24+0+0;6bda,%1,1:40x24+40+0}'
|
||||
$TMUX select-layout "$nested" || fail "nested checksums were rejected"
|
||||
must_equal "$($TMUX display-message -p '#{window_layout}')" "$layout"
|
||||
|
||||
# A bad nested checksum is rejected without changing the current layout.
|
||||
badnested='2fa4,80x24+0+0{dead,%0,0:39x24+0+0;6bda,%1,1:40x24+40+0}'
|
||||
must_fail $TMUX select-layout "$badnested"
|
||||
must_equal "$($TMUX display-message -p '#{window_layout}')" "$layout"
|
||||
|
||||
# Hidden state is retained in the serialized cell flags.
|
||||
hidden='659f,80x24+0+0{%0,0:39x24+0+0;%1,1:40x24+40+0:h}'
|
||||
$TMUX select-layout "$hidden" || fail "hidden layout was rejected"
|
||||
must_equal "$($TMUX display-message -p '#{window_layout}')" "$hidden"
|
||||
|
||||
# Pane IDs map by identity when all match and by tree order when none match.
|
||||
disjoint='80x24+0+0{%100,0:39x24+0+0;%101,1:40x24+40+0}'
|
||||
$TMUX select-layout "$disjoint" || fail "disjoint pane IDs were rejected"
|
||||
must_equal "$($TMUX display-message -p '#{window_layout}')" "$layout"
|
||||
identity='80x24+0+0{%1,0:39x24+0+0;%0,1:40x24+40+0}'
|
||||
$TMUX select-layout "$identity" || fail "matching pane IDs were rejected"
|
||||
must_equal "$($TMUX display-message -p -t %0 '#{pane_left}')" 40
|
||||
$TMUX select-layout "$legacy" || fail "legacy layout could not be restored"
|
||||
|
||||
# Duplicate or partially matching IDs fail when pane counts match.
|
||||
must_fail $TMUX select-layout \
|
||||
'80x24+0+0{%0,0:39x24+0+0;%100,1:40x24+40+0}'
|
||||
must_fail $TMUX select-layout \
|
||||
'80x24+0+0{%100,0:39x24+0+0;%100,1:40x24+40+0}'
|
||||
|
||||
# A target with fewer panes drops cells from the end, compacts z-indexes and
|
||||
# assigns the remaining cells in tree order. A target with more panes fails.
|
||||
fewer='80x24+0+0{%100,1:26x24+0+0;%101,2:26x24+27+0;%102,0:26x24+54+0:z}'
|
||||
$TMUX select-layout "$fewer" || fail "surplus current cells were not removed"
|
||||
must_equal "$($TMUX display-message -p -t %0 '#{pane_left},#{pane_width}')" \
|
||||
'0,26'
|
||||
must_equal "$($TMUX display-message -p -t %1 '#{pane_left},#{pane_width}')" \
|
||||
'27,53'
|
||||
must_equal "$($TMUX display-message -p -t %0 '#{pane_zoomed_flag}')" 0
|
||||
case "$($TMUX display-message -p '#{window_layout}')" in
|
||||
*'%0,0:26x24+0+0;%1,1:53x24+27+0}'*) ;;
|
||||
*) fail "z-indexes were not compacted after removing a cell" ;;
|
||||
esac
|
||||
must_fail $TMUX select-layout '%100,0:80x24+0+0'
|
||||
$TMUX select-layout "$legacy" || fail "legacy layout could not be restored"
|
||||
|
||||
# Mixed legacy and new separators, duplicate z-indices and unknown flags fail.
|
||||
must_fail $TMUX select-layout \
|
||||
'93ed,80x24+0+0{%0,0:39x24+0+0,%1,1:40x24+40+0}'
|
||||
must_fail $TMUX select-layout \
|
||||
'93ec,80x24+0+0{%0,0:39x24+0+0;%1,0:40x24+40+0}'
|
||||
must_fail $TMUX select-layout \
|
||||
'0e42,80x24+0+0{%0,0:39x24+0+0:q;%1,1:40x24+40+0}'
|
||||
|
||||
# Truncated and overflowing values must fail without terminating the server.
|
||||
must_fail $TMUX select-layout '0000,'
|
||||
must_fail $TMUX select-layout \
|
||||
'c523,999999999999999999999999x24,0,0,0'
|
||||
must_fail $TMUX select-layout \
|
||||
'80x24+0+0{%0,0:0x24+0+0;%1,1:40x24+40+0}'
|
||||
must_fail $TMUX select-layout \
|
||||
'80x24+0+0{%0,0:39x24+2147483647+0;%1,1:40x24+40+0}'
|
||||
must_fail $TMUX select-layout \
|
||||
'80x24+0+0{%0,0:39x24+0+0;%1,1:40x24+-10001+0}'
|
||||
$TMUX list-windows >/dev/null || fail "server exited after invalid layouts"
|
||||
|
||||
# Invalid custom layouts must not unzoom the target window.
|
||||
$TMUX resize-pane -t %0 -Z || fail "zoom failed"
|
||||
must_fail $TMUX select-layout \
|
||||
'80x24+0+0{%0,0:0x24+0+0;%1,1:40x24+40+0}'
|
||||
must_equal "$($TMUX display-message -p -t %0 '#{pane_zoomed_flag}')" 1
|
||||
$TMUX resize-pane -t %0 -Z || fail "unzoom failed"
|
||||
|
||||
# X-style geometry supports right/bottom-relative offsets, absolute negative
|
||||
# offsets, doubled plus signs, and omitted positions.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX new-session -d -x 120 -y 40 || exit 1
|
||||
$TMUX new-pane -d -x 20 -y 8 'sleep 100' || fail "floating pane failed"
|
||||
relative='1442,120x40+0+0{%0,1:120x40+0+0;%1,0:30x10-10-20:f}'
|
||||
$TMUX select-layout "$relative" || fail "relative geometry was rejected"
|
||||
must_equal "$($TMUX display-message -p -t %1 '#{pane_left},#{pane_top}')" \
|
||||
'80,10'
|
||||
must_equal "$($TMUX display-message -p '#{window_layout}')" \
|
||||
'0041,120x40+0+0{%0,1:120x40+0+0;%1,0:30x10+80+10:f}'
|
||||
|
||||
absolute='3a22,120x40+0+0{%0,1:120x40+0+0;%1,0:20x8+-10+-20:f}'
|
||||
$TMUX select-layout "$absolute" || fail "absolute negative geometry failed"
|
||||
must_equal "$($TMUX display-message -p -t %1 '#{pane_left},#{pane_top}')" \
|
||||
'-10,-20'
|
||||
must_equal "$($TMUX display-message -p '#{window_layout}')" "$absolute"
|
||||
|
||||
defaults='fcb6,120x40{%0,1:120x40;%1,0:30x10++80+10:f}'
|
||||
$TMUX select-layout "$defaults" || fail "default or doubled offsets failed"
|
||||
must_equal "$($TMUX display-message -p '#{window_layout}')" \
|
||||
'0041,120x40+0+0{%0,1:120x40+0+0;%1,0:30x10+80+10:f}'
|
||||
|
||||
oneoffset='8f93,120x40{%0,1:120x40;%1,0:30x10+80:f}'
|
||||
$TMUX select-layout "$oneoffset" || fail "single offset default failed"
|
||||
must_equal "$($TMUX display-message -p '#{window_layout}')" \
|
||||
'5fb9,120x40+0+0{%0,1:120x40+0+0;%1,0:30x10+80+0:f}'
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX new-session -d -x 80 -y 24 || exit 1
|
||||
$TMUX split-window -h || fail "split-window failed"
|
||||
|
||||
# Multiple @window-id:layout records may be applied in one command. The
|
||||
# newline produced by list-windows is insignificant whitespace.
|
||||
$TMUX new-window -d -n second || fail "second window failed"
|
||||
$TMUX split-window -d -h -t @1 || fail "second window split failed"
|
||||
layouts=$($TMUX list-windows -F '#{window_id}:#{window_layout}')
|
||||
$TMUX select-layout "$layouts" || fail "multiple-window layout was rejected"
|
||||
must_equal "$($TMUX list-windows -F '#{window_id}:#{window_layout}')" \
|
||||
"$layouts"
|
||||
compact=$(printf %s "$layouts" | tr -d '\n')
|
||||
$TMUX select-layout "$compact" || fail "compact multiple-window layout failed"
|
||||
|
||||
# All records are validated before any window is changed.
|
||||
before=$($TMUX display-message -p -t @0 '#{window_width}x#{window_height}')
|
||||
must_fail $TMUX select-layout "@0:$reported@1:0000,"
|
||||
must_equal "$($TMUX display-message -p -t @0 '#{window_width}x#{window_height}')" \
|
||||
"$before"
|
||||
must_fail $TMUX select-layout "@0:$legacy@0:$legacy"
|
||||
must_fail $TMUX select-layout "@999999:$legacy"
|
||||
|
||||
# Add floating panes and verify exact z-order, semicolon output and zoom state.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX new-session -d -x 120 -y 40 || exit 1
|
||||
$TMUX split-window -h || fail "split-window failed"
|
||||
$TMUX new-pane -d -x 30 -y 10 -X 10 -Y 5 'sleep 100' || \
|
||||
fail "first floating pane failed"
|
||||
$TMUX new-pane -d -x 24 -y 8 -X 50 -Y 12 'sleep 100' || \
|
||||
fail "second floating pane failed"
|
||||
|
||||
layout=$($TMUX display-message -p '#{window_layout}')
|
||||
case "$layout" in
|
||||
*'<'*|*'>'*) fail "obsolete floating delimiters were emitted" ;;
|
||||
esac
|
||||
case "$layout" in
|
||||
*';'*',0:'*':f'*) ;;
|
||||
*) fail "new layout does not contain semicolons and front z-index: $layout" ;;
|
||||
esac
|
||||
case "$layout" in
|
||||
*',1:'*':f'*) ;;
|
||||
*) fail "new layout does not contain the second floating z-index: $layout" ;;
|
||||
esac
|
||||
$TMUX select-layout "$layout" || fail "new layout did not round trip"
|
||||
must_equal "$($TMUX display-message -p '#{window_layout}')" "$layout"
|
||||
|
||||
$TMUX resize-pane -t %2 -Z || fail "zoom failed"
|
||||
zoomed=$($TMUX display-message -p '#{window_layout}')
|
||||
case "$zoomed" in
|
||||
*':fz'*) ;;
|
||||
*) fail "zoom flag was not emitted: $zoomed" ;;
|
||||
esac
|
||||
$TMUX select-layout "$zoomed" || fail "zoomed layout did not round trip"
|
||||
must_equal "$($TMUX display-message -p '#{window_layout}')" "$zoomed"
|
||||
must_equal "$($TMUX display-message -p -t %2 '#{pane_zoomed_flag}')" 1
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
169
tmux.1
169
tmux.1
@@ -2681,14 +2681,14 @@ For example:
|
||||
.Bd -literal -offset indent
|
||||
$ tmux list\-windows
|
||||
0: ksh [159x48]
|
||||
layout: bb62,159x48,0,0{79x48,0,0,79x48,80,0}
|
||||
$ tmux select\-layout \[aq]bb62,159x48,0,0{79x48,0,0,79x48,80,0}\[aq]
|
||||
layout: 8ebd,159x48+0+0{%0,0:79x48+0+0;%1,1:79x48+80+0}
|
||||
$ tmux select\-layout \[aq]8ebd,159x48+0+0{%0,0:79x48+0+0;%1,1:79x48+80+0}\[aq]
|
||||
.Ed
|
||||
.Pp
|
||||
.Nm
|
||||
automatically adjusts the size of the layout for the current window size.
|
||||
Note that a layout cannot be applied to a window with more panes than that
|
||||
from which the layout was originally defined.
|
||||
A current or legacy layout can be applied to a window with the same number or
|
||||
fewer panes; surplus layout cells are discarded.
|
||||
.Pp
|
||||
Commands related to windows and panes are as follows:
|
||||
.Bl -tag -width Ds
|
||||
@@ -3371,6 +3371,13 @@ specifies the sort order: one of
|
||||
(time).
|
||||
.Fl r
|
||||
reverses the sort order.
|
||||
.Pp
|
||||
The layout of the window containing each pane may be included using the
|
||||
.Ql window_layout
|
||||
format variable.
|
||||
It is repeated for each pane from the same window.
|
||||
Its format is described under
|
||||
.Ic select\-layout .
|
||||
.Tg lsw
|
||||
.It Xo Ic list\-windows
|
||||
.Op Fl ar
|
||||
@@ -3406,6 +3413,22 @@ or
|
||||
(time).
|
||||
.Fl r
|
||||
reverses the sort order.
|
||||
.Pp
|
||||
The default format includes the layout of each window, also available with the
|
||||
.Ql window_layout
|
||||
format variable.
|
||||
The format is described under
|
||||
.Ic select\-layout .
|
||||
.Pp
|
||||
Layouts for multiple windows may be produced in a form suitable for
|
||||
.Ic select\-layout
|
||||
using
|
||||
.Fl F .
|
||||
For example, they may be captured and reapplied directly:
|
||||
.Bd -literal -offset indent
|
||||
$ layouts=\[dq]$(tmux list\-windows \-F \[aq]#{window_id}:#{window_layout}\[aq])\[dq]
|
||||
$ tmux select\-layout \[dq]$layouts\[dq]
|
||||
.Ed
|
||||
.Tg movep
|
||||
.It Xo Ic move\-pane
|
||||
.Op Fl bdfhMv
|
||||
@@ -3965,6 +3988,142 @@ commands.
|
||||
applies the last set layout if possible (undoes the most recent layout change).
|
||||
.Fl E
|
||||
spreads the current pane and any panes next to it out evenly.
|
||||
.Pp
|
||||
.Ar layout\-name
|
||||
may be a serialized layout with an optional outer checksum followed by one
|
||||
root layout cell:
|
||||
.Bd -literal -offset indent
|
||||
layout = [checksum,]cell
|
||||
.Ed
|
||||
.Pp
|
||||
A layout cell is either a container cell or a pane cell.
|
||||
Geometry gives a cell's size and position.
|
||||
.Nm
|
||||
accepts X Window System-style geometry specifications:
|
||||
.Bd -literal -offset indent
|
||||
geometry = widthxheight[x-offset[y-offset]]
|
||||
x-offset = +N | ++N | +-N | -N
|
||||
y-offset = +N | ++N | +-N | -N
|
||||
.Ed
|
||||
.Pp
|
||||
.Ql +N
|
||||
is an absolute offset from the left or top of the window.
|
||||
.Ql -N
|
||||
is accepted only for floating pane cells and places the pane's right or bottom
|
||||
edge relative to the right or bottom of the window.
|
||||
To specify a negative offset to place a floating pane off the left or above the
|
||||
top of the window, specify it as:
|
||||
.Ql +-N .
|
||||
.Ql ++N
|
||||
is equivalent to
|
||||
.Ql +N .
|
||||
If both offsets are omitted,
|
||||
.Ql +0+0
|
||||
is assumed; if only the first is present, the second defaults to
|
||||
.Ql +0 .
|
||||
Widths and heights must be between 1 and 10,000, and resolved offsets must be
|
||||
between -10,000 and 10,000.
|
||||
Neither
|
||||
.Ic list\-windows
|
||||
nor
|
||||
.Ic list\-panes
|
||||
currently generates
|
||||
.Ql -N
|
||||
relative offsets.
|
||||
.Pp
|
||||
Container cells begin with geometry and hold other layout cells.
|
||||
Braces arrange the child cells from left to right and square brackets arrange
|
||||
them from top to bottom.
|
||||
Pane cells are leaves and begin with a pane ID and z-index, followed by their
|
||||
geometry and optional flags.
|
||||
Semicolons separate child cells.
|
||||
The complete cell forms are:
|
||||
.Bd -literal -offset indent
|
||||
cell = container | pane
|
||||
container = geometry{cell;cell;...}
|
||||
| geometry[cell;cell;...]
|
||||
pane = %pane-id,z-index:geometry[:flags]
|
||||
.Ed
|
||||
.Pp
|
||||
Pane IDs in the current format must be unique.
|
||||
If the layout and target window have the same number of panes and the pane IDs
|
||||
exactly match those in the target window, panes are assigned by ID.
|
||||
If none match, panes are assigned in tree order, allowing a layout from another
|
||||
window to be reused; a partial match is rejected as ambiguous.
|
||||
If the target window has fewer panes, cells are removed from the end of the
|
||||
layout tree until the counts match, remaining z-indexes are compacted, and
|
||||
panes are assigned in tree order.
|
||||
A layout cannot be applied to a target window with more panes than it has pane
|
||||
cells.
|
||||
.Pp
|
||||
If a window has only one pane, the root layout cell is itself a pane cell, so
|
||||
the pane ID, z-index, geometry and any flags immediately follow the outer
|
||||
checksum.
|
||||
The z-index is zero for the front pane and increases towards the back.
|
||||
It is present for every pane, including tiled panes.
|
||||
Z-indexes must be unique and contiguous from zero, with all floating panes
|
||||
before any tiled panes.
|
||||
The flags are
|
||||
.Ql f
|
||||
for floating,
|
||||
.Ql h
|
||||
for hidden and
|
||||
.Ql z
|
||||
for zoomed.
|
||||
An absent
|
||||
.Ql f
|
||||
flag means tiled and absent
|
||||
.Ql h
|
||||
and
|
||||
.Ql z
|
||||
flags mean visible and unzoomed.
|
||||
The
|
||||
.Ql h
|
||||
and
|
||||
.Ql z
|
||||
flags cannot be combined.
|
||||
.Pp
|
||||
For example, this layout contains two tiled panes and one floating pane:
|
||||
.Bd -literal -offset indent
|
||||
6939,120x40+0+0{%0,1:60x40+0+0;%1,2:59x40+61+0;%2,0:28x8+11+6:f}
|
||||
.Ed
|
||||
.Pp
|
||||
The legacy format uses commas rather than semicolons to separate child cells
|
||||
and does not include z-indexes or flags:
|
||||
.Bd -literal -offset indent
|
||||
legacy-geometry = widthxheight,x,y
|
||||
cell = legacy-geometry{cell,cell,...}
|
||||
| legacy-geometry[cell,cell,...]
|
||||
| legacy-geometry,pane-id
|
||||
.Ed
|
||||
.Pp
|
||||
The checksum is optional.
|
||||
If present, it is four hexadecimal digits and covers all non-whitespace text
|
||||
after its comma; a layout with an incorrect checksum is rejected.
|
||||
Whitespace, including newlines, may appear between layout tokens and does not
|
||||
change the checksum, so a layout may be indented for readability.
|
||||
A checksum may also appear before any nested cell; nested checksums are
|
||||
accepted and checked but are not generated by
|
||||
.Ic list\-windows .
|
||||
A layout must use one form consistently.
|
||||
In a legacy layout, panes are treated as tiled, visible and unzoomed.
|
||||
.Ic list\-windows
|
||||
and the
|
||||
.Ql window_layout
|
||||
format variable generate the current format with a checksum.
|
||||
.Pp
|
||||
A serialized layout is a layout snapshot, not a complete session restoration.
|
||||
It rearranges existing panes and windows but does not create panes or windows.
|
||||
.Pp
|
||||
Layouts for multiple windows may be prefixed by their window IDs and joined:
|
||||
.Bd -literal -offset indent
|
||||
window-layouts = @window-id:layout [@window-id:layout ...]
|
||||
.Ed
|
||||
.Pp
|
||||
Whitespace, including newlines, may appear between the window records.
|
||||
Each window layout retains its own checksum.
|
||||
All records are checked before any window is changed.
|
||||
Unknown or duplicate window IDs are rejected.
|
||||
.Tg selectp
|
||||
.It Xo Ic select\-pane
|
||||
.Op Fl DdeLlMmRUZ
|
||||
@@ -7111,7 +7270,7 @@ The following variables are available, where appropriate:
|
||||
.It Li "window_id" Ta "" Ta "Unique window ID"
|
||||
.It Li "window_index" Ta "#I" Ta "Index of window"
|
||||
.It Li "window_last_flag" Ta "" Ta "1 if window is the last used"
|
||||
.It Li "window_layout" Ta "" Ta "Window layout description, ignoring zoomed window panes"
|
||||
.It Li "window_layout" Ta "" Ta "Window layout description, including zoom and pane flags"
|
||||
.It Li "window_linked" Ta "" Ta "1 if window is linked across sessions"
|
||||
.It Li "window_linked_sessions" Ta "" Ta "Number of sessions this window is linked to"
|
||||
.It Li "window_linked_sessions_list" Ta "" Ta "List of sessions this window is linked to"
|
||||
|
||||
7
tmux.h
7
tmux.h
@@ -1502,7 +1502,13 @@ struct layout_cell {
|
||||
enum layout_type type;
|
||||
|
||||
#define LAYOUT_CELL_FLOATING 0x1
|
||||
#define LAYOUT_CELL_HIDDEN 0x2
|
||||
#define LAYOUT_CELL_ZOOMED 0x4 /* only while parsing a layout */
|
||||
#define LAYOUT_CELL_X_RELATIVE 0x8 /* only while parsing a layout */
|
||||
#define LAYOUT_CELL_Y_RELATIVE 0x10 /* only while parsing a layout */
|
||||
int flags;
|
||||
u_int z_index; /* only while parsing a layout */
|
||||
u_int pane_id; /* only while parsing a layout */
|
||||
|
||||
struct layout_cell *parent;
|
||||
|
||||
@@ -3711,6 +3717,7 @@ int layout_remove_tile(struct window *, struct layout_cell *);
|
||||
|
||||
/* layout-custom.c */
|
||||
char *layout_dump(struct window *, struct layout_cell *);
|
||||
int layout_validate(struct window *, const char *, char **);
|
||||
int layout_parse(struct window *, const char *, char **);
|
||||
|
||||
/* layout-set.c */
|
||||
|
||||
6
window.c
6
window.c
@@ -1641,6 +1641,12 @@ window_pane_key(struct window_pane *wp, struct client *c, struct session *s,
|
||||
int
|
||||
window_pane_is_visible(struct window_pane *wp)
|
||||
{
|
||||
struct layout_cell *lc = wp->layout_cell;
|
||||
|
||||
if (lc == NULL)
|
||||
lc = wp->saved_layout_cell;
|
||||
if (lc != NULL && (lc->flags & LAYOUT_CELL_HIDDEN))
|
||||
return (0);
|
||||
if (~wp->window->flags & WINDOW_ZOOMED)
|
||||
return (1);
|
||||
return (wp == wp->window->active);
|
||||
|
||||
Reference in New Issue
Block a user