Writes tokens to an underlying writer, respecting certain formatting settings and a maximum line width.

The FormattingWriter manages the following aspects:

  • Indentation
  • Line Breaking
  • Spacing

Additionally, it also writes comments if a token stream is given.

Indentation

Two indentation levels are associated with each token: one before the token, and one after it. Each token also introduces a context which tracks these indentation levels if they stack. In this case, their indentation is applied to all subsequent lines until the context is closed.

By default, the indentation before the token never stacks, and the indentation after the token always does. However, either indentation may also stack in the other case, or only if it is actually applied, that is, only if that token is the first/last of its line.

When the token is written, its context (instance of FormattingContext) is pushed onto a context stack. The indentation of each line is then the sum of all indentation currently on the stack, plus the indentation before the current token and after the last token (if they’re not already on the stack). When a context is closed, the context and all contexts on top of it are removed from the context stack.

A context can be closed in two ways:

  1. By associating it with a token. For example, you would say that a closing brace } closes the context of the corresponding opening brace {: The block has ended, and subsequent lines should no longer be indented as if they were still part of the block. Tokens that close another token’s context may not introduce indentation themselves, since they don’t get a context of their own.
  2. By calling closeContext().

You can also obtain a context not associated with any token by calling openContext(), which introduces some undirectional indentation that always stacks. This is mostly useful if you have a closing token with no designated opening token: for example, a statement’s closing semicolon ; should close some context, but there is no corresponding token which opens that context.

Examples:

  • An opening brace { has an indentAfter of 1, which always stacks. The resulting context is closed by the associated closing brace }.
  • A member operator . has an indentBefore of 1, which never stacks. If it stacked, you would get this:
    value someValue = something
      .foo(thing)
          .bar(otherThing)
              .baz(whyIsThisSoIndented);
    
  • A refinement operator => has an indentBefore of 2, which stacks only if it is applied. Thus, you get both of the following results:
    Integer i => function(
      longArgument // only indented by one level, from the (
    );
    Integer f(String param1, String param2)
          => let (thing = param1.length, thing2 = param2.uppercased)
              thing + thing2.string; // indented from both => and let
    

Line Breaking

Two Integer ranges are associated with each token. One indicates how many line breaks may occur before the token, and the other indicates how many may occur after the token. Additionally, one may call requireAtLeastLineBreaks() to further restrict how many line breaks may occur between two tokens.

The intersection of these ranges for the border between two tokens is then used to determine how many line breaks should be written before the token.

  • If tokens exists, then each time a token is written, the token stream is fast-forwarded until the token is met (if a token with a different text is met, an exception is thrown). In fast-forwarding, the amount of line breaks is counted. After fast-forwarding has finished, the number of line breaks that were counted is clamped into the line break range, and this many line breaks are written.
  • If tokens doesn’t exist, then the first element of the range is used (usually the lowest, unless the range is decreasing).

(Internally, the FormattingWriter also keeps track if a line break range came from writeToken(), requireAtLeastLineBreaks(), or was added internally when dealing with comments; an empty intersection of two ranges is usually a bug, unless one of the ranges comes from a comment, in which case we just use that range instead.)

Additionally, the FormattingWriter also breaks lines according to a maximum line length and a LineBreakStrategy, as determined by options. To achieve this, tokens are not directly written to the underlying writer; instead, they are added to a token queue (not to be confused with the token stack, which is used for indentation). Each time a token is added, the FormattingWriter checks if there are enough tokens on the queue for the line break strategy to decide where a line break should be placed. Line breaks are allowed between tokens if their respecive ranges included at least one value greater than zero (in other words, to disallow a line breaks between two tokens, pass a range of 0..0 to either of them). When a line break location is known, that line is written and its tokens removed from the queue (their contexts are then added to the token stack).

Spacing

If you’ve made it this far, relax, this is the easiest section :)

Two Integers are associated with each token. One indicates the token’s desire to have a space before it, the other indicates the desire to have a space after it. When the two tokens are written, these integers are added, and if the sum is >= 0, then a space is written.

To avoid inverting the intended result by numerical overflow, don’t use values outside the range minDesire..maxDesire. You can also give false and true to writeToken(), which are convenient and readable syntax sugar for these two values (spaceBefore = true).

Comments

The fast-forwarding of the token stream (if given) was already mentioned in the “Line Breaking” section. If comment tokens are encountered during the fast-forwarding, they are written out like tokens with

no subtypes hierarchy

Initializer
FormattingWriter(TokenStream? tokens, Writer writer, FormattingOptions options)
Attributes
tokensshared TokenStream? tokens
Inherited Attributes
Attributes inherited from: Object
hash, string
Methods
closeContextshared void closeContext(FormattingContext context)
destroyshared actual void destroy(Throwable? error)

Enqueue a line break if the last queue element isn’t a line break, then flush the queue.

Refines Destroyable.destroy
openContextshared FormattingContext openContext(Integer indentAfter = ...)

Open a FormattingContext not associated with any token.

Parameters:
  • indentAfter = x
requireAtLeastLineBreaksshared void requireAtLeastLineBreaks(Integer limit, Boolean fastForwardFirst = ...)

Require at least limit line breaks between the latest token and the next one to be written.

Parameters:
  • fastForwardFirst = the

    If true, fastForward the token stream before intersecting the line breaks. This makes a difference if there are comments between the latest and the next token; with fast-forwarding, the intersection will be applied between the comments and the next token, while without it, the intersection will be applied between the latest token and the comments.

requireAtMostLineBreaksshared void requireAtMostLineBreaks(Integer limit, Boolean fastForwardFirst = ...)

Require at most limit line breaks between the latest token and the next one to be written.

Parameters:
  • fastForwardFirst = the

    If true, fastForward the token stream before intersecting the line breaks. This makes a difference if there are comments between the latest and the next token; with fast-forwarding, the intersection will be applied between the comments and the next token, while without it, the intersection will be applied between the latest token and the comments.

writeTokenshared FormattingContext? writeToken(AntlrToken|String token, FormattingContext? context = ..., Integer indentBefore = ..., Integer indentAfter = ..., StackCondition stackIndentBefore = ..., StackCondition stackIndentAfter = ..., Range<Integer> lineBreaksBefore = ..., Range<Integer> lineBreaksAfter = ..., Integer|Boolean spaceBefore = ..., Integer|Boolean spaceAfter = 0, AntlrToken|String tokenInStream = ...)

Add a single token, then try to write out a line.

See the class documentation for more information on the token model and how the various parameters of a token interact.

All parameters (except token, of course) default to a “save to ignore” / “don’t care” value.

Parameters:
  • token

    The token.

  • context = val

    The context that this token closes. If this value isn’t null, then this token will not itself open a new context, and the method will therefore return null.

  • indentBefore = g

    The indentation before this token.

  • indentAfter

    The indentation after this token.

  • stackIndentBefore = ull,

    The condition under which to stack the indentation before this token. By default, the indentation before a token never stacks.

  • stackIndentAfter = i

    The condition under which to stack the indentation after this token. By default, the indentation after a token always stacks.

  • lineBreaksBefore = Inde

    The amount of line breaks that is allowed before this token.

  • lineBreaksAfter = ackI

    The amount of line breaks that is allowed after this token.

  • spaceBefore

    Whether to put a space before this token.

    true and false are sugar for maxDesire and minDesire, respectively.

  • spaceAfter = 0

    Whether to put a space after this token.

    true and false are sugar for maxDesire and minDesire, respectively.

  • tokenInStream = er =

    The token that is expected to occur in the token stream for this token.

    In virtually all cases, this is the same as token; however, for identifiers, the \i/\I that is sometimes part of the code isn’t included in the token text, so in this case you would pass, for example, \ivalue as token and value as tokenInStream,

Inherited Methods
Methods inherited from: Object
equals
Methods inherited from: Destroyable
destroy
Nested Aliases
QueueElementshared QueueElement=> Token|Empty|LineBreak
Nested Interfaces
ClosingElementshared ClosingElement
Elementshared Element
FormattingContextshared FormattingContext
OpeningElementshared OpeningElement
Nested Classes
ClosingTokenshared ClosingToken
Emptyshared abstract Empty
InvariantTokenshared InvariantToken
LineBreakshared LineBreak
OpeningTokenshared OpeningToken
Tokenshared abstract Token