The formatter works by attempting to select an appropriate
wrap (collectively referred to as a “layout”) for each node in the layout
tree. Positions are represented by
(row, col) pairs and the wrap dictates
how childen of that node are positioned.
cmake-format implements three styles of wrapping.
The default wrapping for all nodes is horizontal wrapping. If horizontal
wrapping fails to emit an admissible layout, then a node will advance to
either vertical wrapping or nested wrapping (which one depends on the type of
Horizontal wrapping is like “word wrap”. Each child is assigned a position immediately following it’s predecessor, so long as that child fits in the remaining space up to the column limit. Otherwise the child is moved to the next line:
| |<- col-limit | ██████ ███ ██ █████ | | ███████████████ ████ | | █████████ ████ |
Note that a line comment can force an early newline:
| |<- col-limit | ██████ ███ # | | ██ █████ | | ███████████████ ████ | | █████████ ████ |
Note that wrapping happens at the depth of the layout tree, so if we have multiple groups of multiple arguments each, then each group will be placed as if it were a single unit:
| |<- col-limit | (██████ ███) (██ █████) | | (███ ██ ███████████████ ████) |
Groups may be parenthetical groups (as above) or keyword groups:
| |<- col-limit | ▒▒▒▒▒▒▒ ███ ▒▒▒ █████ | | ▒▒▒▒▒ ██ ███████████████ ████ |
or any other grouping assigned by the parser.
In the event that a subgroup cannot be packed within a single line of full column width, it will be wrapped internally, and the next group placed on the next line:
| |<- col-limit | ▒▒▒▒▒ ███ ▒▒▒▒ █████ | | ▒▒▒▒ ██ ███████████████ ████ | | ██ █████ | | ▒▒▒▒▒▒▒ ██ ██ █ ▒▒ █ |
In particular the following is never a valid packing (where the two groups are siblings) in the layout tree:
| |<- col-limit | ▒▒▒ █████ ▒▒▒ ██ | | ███████████████ | | ████ ██ █████ |
Vertical wrapping assigns each child to the next row:
██████ ███ ██ █████ ███████████████ ████
Again, note that this happens at the depth of the layout tree. In particular children may be wrapped horizontally within the subtrees:
| ▒▒▒▒▒▒ ███ ██████ |<- col-limit | ▒▒▒ ██████ ██ | | ▒▒▒▒ ████ █████ ██████ | | ██████ ██████ | | ████ ██████████ | | ▒▒ ███ ████ |
Nesting places children in a column which is one
tab_width to the
right of the parent node’s position, and one line below. For example:
| |<- col-limit | ▒▒▒▒▒ | | ██ ███ ██ █████ | | ████████████████ | | █████████ ████ |
In a more deeply nested layout tree, we might see the following:
| |<- col-limit | ▓▓▓▓▓ | | ▒▒▒▒▒ | | ██ ███ ██ █████ | | ████████████████ | | █████████ ████ | | ▒▒▒ | | ████ ███ █ | | ▒▒▒▒▒▒ | | ████ ███ █ |
Depending on how
cmake-format is configured, elements at different depths
may be nested differently. For example:
| |<- col-limit | ▓▓▓▓▓ | | ▒▒▒▒▒ ██ ███ ██ █████ | | ████████████████ | | █████████ ████ | | ▒▒▒ ████ ███ █ | | ▒▒▒▒▒▒ ████ ███ █ |
Note that the only nodes that can nest are
nodes. These nodes necessarily only have one child, an
Therefore there really isn’t a notion of “wrapping” for these nodes.
For top-level nodes in the layout tree (i.e.
FLOW_CONTROL, etc…) the positioning is straight forward and
these nodes are laid out in a single pass. Each child is positioned on the
first line after the output cursor of it’s predecessor, and at a column
config.format.tab_size to the right of it’s parent.
STATEMENTS however, are laid out over several passes until the
text for that subtree is accepted. Each pass is governed by a
specification mapping pass number to a wrap decision (i.e. a
boolean indicating whether or not to wrap vertical or nest children)
The current algorithm works in a kind of top-down refinement. When a node is
laid out by calling it’s
reflow() method, it is informed of its parent’s
current pass number (
passno). It then iterates through its own
from zero up to it’s parent’s
passno and terminates at the first admissible
layout. Note that within the layout of the node itself, it’s current
passno can only affect its
wrap decision. However, because each of its
children will advance through their own passes, the overall layout of a subtree
between two different passes may change, even if the node at the subtree root
didn’t change it’s
wrap decision between those passes.
When a node is in horizontal layout mode (
wrap=False), there are a couple
of reasons why the algorithm might choose to insert a newline between two
of it’s children.
- If a token would overflow the column limit, insert a newline (e.g. the usual notion of wrapping)
- If the token is the last token before a closing parenthesis, and the token plus the parenthesis would overflow the column limit, then insert a newline.
- If a token is preceeded by a line comment, then the token cannot be placed on the same line as the comment (or it will become part of the comment) so a newline is inserted between them.
- If a token is a line comment which is not associated with an argument (e.g. it is a “free” comment at the current scope) then it will not be placed on the same line as a preceeding argument token. If it was, then subsequent parses would associate this comment with that argument. In such a case, a newline is inserted between the preceeding argument and the line comment.
- If the node is an interior node, and one of it’s children is internally wrapped (i.e. consumes more than two lines) then it will not be placed on the same line as another node. In such a case a newlines is inserted.
- If the node is an interior node and a child fails to find an admissible layout at the current cursor, a newline is inserted and a new layout attempt is made for the child.
There are a couple of reasons why a layout may be deemed inadmissible:
- If the bounding box of a node overflows the column limit
- If a node is horizontally wrapped at the current
passnobut consumes more than
- If the node is horizontally wrapped at the current
passnobut the node path is marked as