diff --git a/layout-custom.c b/layout-custom.c index 7c9c68bdc..ada162055 100644 --- a/layout-custom.c +++ b/layout-custom.c @@ -25,6 +25,63 @@ #include "tmux.h" +/* + * Layout string syntax + * -------------------- + * + * A layout has one outer cell and may have a four-digit hexadecimal checksum: + * + * layout = [checksum,](v2:new-cell | old-cell) + * + * The checksum covers all non-whitespace text after its comma, including the + * version. Checksums before nested cells are also accepted, although they are + * not generated. The v2: prefix is accepted only at the outer level and + * requires the new cell form. The new cell form requires v2: and unknown + * versions are rejected. + * + * New form: + * + * geometry = widthxheight[x-position[y-position]] + * container = geometry{new-cell;new-cell;...} + * | geometry[new-cell;new-cell;...] + * pane = %pane-id,z-index:geometry[:flags] + * new-cell = container | pane + * + * Braces arrange children from left to right and square brackets from top to + * bottom. Semicolons separate children. Positions use X geometry: +N and ++N + * are absolute, +-N is negative, and -N places the right or bottom edge N + * cells from the right or bottom. A missing position is +0; if only one is + * present, the second is +0. Relative right or bottom positions are permitted + * only for floating panes. Widths and heights are between PANE_MINIMUM and + * PANE_MAXIMUM; resolved positions are between -PANE_MAXIMUM and + * PANE_MAXIMUM. + * + * Pane IDs must be unique. Z-indexes must be unique and contiguous from zero, + * with floating panes before tiled panes. Flags are f (floating), h (hidden) + * and z (zoomed); h and z cannot be combined. Hidden panes contain their + * restoration geometry. + * + * Old form: + * + * geometry = widthxheight,x-position,y-position + * old-cell = geometry{old-cell,old-cell,...} + * | geometry[old-cell,old-cell,...] + * | geometry[,pane-id] + * + * Old pane IDs are optional and ignored. Old panes are tiled, visible and + * unzoomed. A layout must not mix the two cell forms or their separators. + * Whitespace may appear between tokens and is ignored by the checksum. + * + * Layouts rearrange existing panes; they do not create panes. New pane IDs are + * used when all match the target window, tree order is used when none match, + * and a partial match is rejected. Surplus cells are removed from the end when + * the target has fewer panes; a target with more panes is rejected. + * + * cmd-select-layout.c also accepts multiple window records in this form: + * + * @window-id:layout [@window-id:layout ...] + */ + #define LAYOUT_STRING_MAX 8192 #define LAYOUT_DEPTH_MAX 64 @@ -949,7 +1006,8 @@ layout_prepare(struct window *w, const char *layout, char **cause) layout_free_cell(root, 0); return (NULL); } - if (version == 1 && ctx.format != LAYOUT_FORMAT) { + if ((version == 1 && ctx.format != LAYOUT_FORMAT) || + (version == 0 && ctx.format == LAYOUT_FORMAT)) { *cause = xstrdup("invalid layout version"); layout_free_cell(root, 0); return (NULL); diff --git a/regress/layout-custom.sh b/regress/layout-custom.sh index a1bf6d15d..757a32359 100755 --- a/regress/layout-custom.sh +++ b/regress/layout-custom.sh @@ -53,10 +53,9 @@ layout=$($TMUX display-message -p '#{window_layout}') must_equal "$layout" \ 'cc3c,v2: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" +# Checksums are optional, but the new form requires its version. +must_fail $TMUX select-layout \ + '80x24+0+0{%0,0:39x24+0+0;%1,1:40x24+40+0}' $TMUX select-layout \ 'v2:80x24+0+0{%0,0:39x24+0+0;%1,1:40x24+40+0}' || \ fail "checksumless versioned layout was rejected" @@ -109,8 +108,8 @@ $TMUX resize-pane -t %0 -Z || fail "tiled unzoom failed" $TMUX select-layout "$layout" || fail "layout could not be restored" # Whitespace and newlines between tokens do not affect the checksum. -indented='93fc, - 80x24 +0 +0 { +indented='cc3c, + v2: 80x24 +0 +0 { %0, 0: 39x24 +0 +0; %1, 1: 40x24 +40 +0 }' @@ -137,12 +136,12 @@ $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}' +nested='v2: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}' +badnested='v2: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" @@ -152,23 +151,23 @@ $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}' +disjoint='v2: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}' +identity='v2: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}' + 'v2: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}' + 'v2: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}' +fewer='v2: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 '#{window_panes}')" 2 must_equal "$($TMUX display-message -p -t %0 '#{pane_left},#{pane_width}')" \ @@ -183,40 +182,40 @@ esac # The complete tree must be structurally valid before surplus cells are # removed; the last child has the wrong height here. must_fail $TMUX select-layout \ - '80x24+0+0{%100,0:39x24+0+0;%101,1:20x24+40+0;%102,2:19x10+61+0}' -must_fail $TMUX select-layout '%100,0:80x24+0+0' + 'v2:80x24+0+0{%100,0:39x24+0+0;%101,1:20x24+40+0;%102,2:19x10+61+0}' +must_fail $TMUX select-layout 'v2:%100,0:80x24+0+0' must_fail $TMUX select-layout '80x24,0,0,0' $TMUX select-layout "$legacy" || fail "legacy layout could not be restored" # Mixed legacy and new syntax, malformed containers, duplicate z-indices, # non-contiguous z-indices, invalid z-ordering and unknown flags fail. must_fail $TMUX select-layout \ - '93ed,80x24+0+0{%0,0:39x24+0+0,%1,1:40x24+40+0}' + 'v2:80x24+0+0{%0,0:39x24+0+0,%1,1:40x24+40+0}' must_fail $TMUX select-layout \ '80x24,0,0{39x24,0,0,0;%1,1:40x24+40+0}' -must_fail $TMUX select-layout '80x24+0+0{}' +must_fail $TMUX select-layout 'v2:80x24+0+0{}' must_fail $TMUX select-layout \ - '80x24+0+0{%0,0:39x24+0+0;}' + 'v2:80x24+0+0{%0,0:39x24+0+0;}' must_fail $TMUX select-layout \ - '80x24+0+0{%0,0:39x24+0+0|%1,1:40x24+40+0}' + 'v2: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}' + 'v2:80x24+0+0{%0,0:39x24+0+0;%1,0:40x24+40+0}' must_fail $TMUX select-layout \ - '80x24+0+0{%0,0:39x24+0+0;%1,2:40x24+40+0}' + 'v2:80x24+0+0{%0,0:39x24+0+0;%1,2:40x24+40+0}' must_fail $TMUX select-layout \ - '80x24+0+0{%0,0:39x24+0+0;%1,1:40x24+40+0:f}' + 'v2:80x24+0+0{%0,0:39x24+0+0;%1,1:40x24+40+0:f}' must_fail $TMUX select-layout \ - '80x24+0+0{%0,0:38x24+0+0;%1,1:40x24+39+0}' + 'v2:80x24+0+0{%0,0:38x24+0+0;%1,1:40x24+39+0}' must_fail $TMUX select-layout \ - '0e42,80x24+0+0{%0,0:39x24+0+0:q;%1,1:40x24+40+0}' + 'v2:80x24+0+0{%0,0:39x24+0+0:q;%1,1:40x24+40+0}' must_fail $TMUX select-layout \ - '80x24+0+0{%0,0:39x24+0+0:ff;%1,1:40x24+40+0}' + 'v2:80x24+0+0{%0,0:39x24+0+0:ff;%1,1:40x24+40+0}' must_fail $TMUX select-layout \ - '80x24+0+0{%0,0:39x24+0+0:hh;%1,1:40x24+40+0}' + 'v2:80x24+0+0{%0,0:39x24+0+0:hh;%1,1:40x24+40+0}' must_fail $TMUX select-layout \ - '80x24+0+0{%0,0:39x24+0+0:zz;%1,1:40x24+40+0}' + 'v2:80x24+0+0{%0,0:39x24+0+0:zz;%1,1:40x24+40+0}' must_fail $TMUX select-layout \ - '80x24+0+0{%0,0:39x24+0+0:z;%1,1:40x24+40+0:z}' + 'v2:80x24+0+0{%0,0:39x24+0+0:z;%1,1:40x24+40+0:z}' must_fail $TMUX select-layout \ '80x24,0,0{39x24,0,0,0,40x24,40,0,}' @@ -225,22 +224,22 @@ 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}' + 'v2: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}' + 'v2: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}' + 'v2:80x24+0+0{%0,0:39x24+0+0;%1,1:40x24+-10001+0}' must_fail $TMUX select-layout \ - '80x24-1+0{%0,0:39x24+0+0;%1,1:40x24+40+0}' + 'v2:80x24-1+0{%0,0:39x24+0+0;%1,1:40x24+40+0}' must_fail $TMUX select-layout \ - '80x24+0+0{%0,0:39x24-1+0;%1,1:40x24+40+0}' + 'v2:80x24+0+0{%0,0:39x24-1+0;%1,1:40x24+40+0}' must_fail $TMUX select-layout \ - '80x24+0+0{%0,0:39x24+;%1,1:40x24+40+0}' + 'v2:80x24+0+0{%0,0:39x24+;%1,1:40x24+40+0}' must_fail $TMUX select-layout \ - '80x24+0+0{%0,0:39x24+0+;%1,1:40x24+40+0}' + 'v2:80x24+0+0{%0,0:39x24+0+;%1,1:40x24+40+0}' must_fail $TMUX select-layout \ - '80x24+0+0{%0,0:39x24++ +0;%1,1:40x24+40+0}' -long='80x24+0+0{%0,0:39x24+0+0;%1,1:40x24+40+0}' + 'v2:80x24+0+0{%0,0:39x24++ +0;%1,1:40x24+40+0}' +long='v2:80x24+0+0{%0,0:39x24+0+0;%1,1:40x24+40+0}' i=0 while [ $i -lt 9000 ]; do long="${long}x" @@ -252,7 +251,7 @@ $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}' + 'v2: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" @@ -269,8 +268,8 @@ while [ $i -lt 64 ]; do deep="1x1+0+0{$deep}" i=$((i + 1)) done -$TMUX select-layout "$deep" || fail "maximum layout depth was rejected" -must_fail $TMUX select-layout "1x1+0+0{$deep}" +$TMUX select-layout "v2:$deep" || fail "maximum layout depth was rejected" +must_fail $TMUX select-layout "v2:1x1+0+0{$deep}" # X-style geometry supports right/bottom-relative offsets, absolute negative # offsets, doubled plus signs, and omitted positions. @@ -289,14 +288,14 @@ case "$($TMUX display-message -p '#{window_layout}')" in *) fail "negative floating pane was not serialized with +- offsets" ;; esac -relative='1442,120x40+0+0{%0,1:120x40+0+0;%1,0:30x10-10-20:f}' +relative='v2: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}')" \ "$(versioned '120x40+0+0{%0,1:120x40+0+0;%1,0:30x10+80+10:f}')" -bottomright='120x40+0+0{%0,1:120x40+0+0;%1,0:30x10-0-0:f}' +bottomright='v2:120x40+0+0{%0,1:120x40+0+0;%1,0:30x10-0-0:f}' $TMUX select-layout "$bottomright" || fail "bottom-right geometry failed" must_equal "$($TMUX display-message -p -t %1 '#{pane_left},#{pane_top}')" \ '90,30' @@ -304,32 +303,32 @@ must_equal "$($TMUX display-message -p '#{window_layout}')" \ "$(versioned '120x40+0+0{%0,1:120x40+0+0;%1,0:30x10+90+30:f}')" absolute_body='120x40+0+0{%0,1:120x40+0+0;%1,0:20x8+-10+-20:f}' -$TMUX select-layout "3a22,$absolute_body" || \ +$TMUX select-layout "v2:$absolute_body" || \ 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}')" \ "$(versioned "$absolute_body")" -hiddenfloat='120x40+0+0{%0,1:120x40+0+0;%1,0:20x8+-10+-20:fh}' +hiddenfloat='v2:120x40+0+0{%0,1:120x40+0+0;%1,0:20x8+-10+-20:fh}' $TMUX select-layout "$hiddenfloat" || fail "hidden floating layout failed" must_equal "$($TMUX display-message -p -t %1 '#{pane_left},#{pane_top},#{pane_width},#{pane_height}')" \ '-10,-20,20,8' must_equal "$($TMUX display-message -p '#{window_layout}')" \ "$(versioned '120x40+0+0{%0,1:120x40+0+0;%1,0:20x8+-10+-20:fh}')" must_fail $TMUX select-layout \ - '120x40+0+0{%0,1:120x40+0+0;%1,0:20x8+-10+-20:fhz}' + 'v2:120x40+0+0{%0,1:120x40+0+0;%1,0:20x8+-10+-20:fhz}' must_equal "$($TMUX display-message -p '#{window_layout}')" \ "$(versioned '120x40+0+0{%0,1:120x40+0+0;%1,0:20x8+-10+-20:fh}')" must_fail $TMUX select-layout \ - '120x40+0+0{%0,1:120x40+0+0;%1,0:10000x8-10000+0:f}' + 'v2:120x40+0+0{%0,1:120x40+0+0;%1,0:10000x8-10000+0:f}' -defaults='fcb6,120x40{%0,1:120x40;%1,0:30x10++80+10:f}' +defaults='v2: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}')" \ "$(versioned '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}' +oneoffset='v2: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}')" \ "$(versioned '120x40+0+0{%0,1:120x40+0+0;%1,0:30x10+80+0:f}')" diff --git a/tmux.1 b/tmux.1 index b5f7696c8..9117c73d6 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3975,142 +3975,25 @@ applies the last set layout if possible (undoes the most recent layout change). spreads the current pane and any panes next to it out evenly. .Pp .Ar layout\-name -may be a layout description with an optional outer checksum followed by one -root layout cell: -.Bd -literal -offset indent -layout = [checksum,][v2:]cell -.Ed -.Pp -The +may be a layout description in either a new or old form, optionally beginning +with a four-digit hexadecimal checksum and a comma. +The new form is identified by .Ql v2: -prefix identifies the new form and is included in generated layouts using the -new form. -It may be omitted when reading a layout in the new form for compatibility -with older output. -An explicit -.Ql v2: -prefix may not be used with the old cell form and unknown versions are -rejected. -.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 new form 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 -.Ql v2: -prefix. -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: +after the checksum, uses semicolons between cells, and begins pane cells with +.Ql % . +For example: .Bd -literal -offset indent 7749,v2:120x40+0+0{%0,1:60x40+0+0;%1,2:59x40+61+0;%2,0:28x8+11+6:f} .Ed .Pp -The old form uses commas rather than semicolons to separate child cells -and does not include z-indexes or flags: +The old form has no +.Ql v2: +prefix, uses commas, and does not include z-indexes or flags. +For example: .Bd -literal -offset indent -old-geometry = widthxheight,x,y -cell = old-geometry{cell,cell,...} - | old-geometry[cell,cell,...] - | old-geometry[,pane-id] +89f5,80x24,0,0{39x24,0,0,0,40x24,40,0,1} .Ed .Pp -Pane IDs are optional in old layouts and are ignored when assigning panes. -This permits layouts from versions of -.Nm -before pane IDs were added. -.Pp -The checksum is optional. -If present, it is four hexadecimal digits and covers all non-whitespace text -after its comma, including any version prefix; 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 . -Version prefixes are accepted only on the outer layout, not on nested cells. -A layout must use one form consistently. -In an old layout, panes are treated as tiled, visible and unzoomed. Outside control mode, .Ql window_layout and