1 
2 //          Copyright Brian Schott 2015.
3 // Distributed under the Boost Software License, Version 1.0.
4 //    (See accompanying file LICENSE.txt or copy at
5 //          http://www.boost.org/LICENSE_1_0.txt)
6 
7 module dfmt.formatter;
8 
9 import dparse.lexer;
10 import dparse.parser;
11 import dparse.rollback_allocator;
12 import dfmt.ast_info;
13 import dfmt.config;
14 import dfmt.indentation;
15 import dfmt.tokens;
16 import dfmt.wrapping;
17 import std.array;
18 
19 /**
20  * Formats the code contained in `buffer` into `output`.
21  * Params:
22  *     source_desc = A description of where `buffer` came from. Usually a file name.
23  *     buffer = The raw source code.
24  *     output = The output range that will have the formatted code written to it.
25  *     formatterConfig = Formatter configuration.
26  * Returns: `true` if the formatting succeeded, `false` of a lexing error. This
27  *     function can return `true` if parsing failed.
28  */
29 bool format(OutputRange)(string source_desc, ubyte[] buffer, OutputRange output,
30         Config* formatterConfig)
31 {
32     LexerConfig config;
33     config.stringBehavior = StringBehavior.source;
34     config.whitespaceBehavior = WhitespaceBehavior.skip;
35     LexerConfig parseConfig;
36     parseConfig.stringBehavior = StringBehavior.source;
37     parseConfig.whitespaceBehavior = WhitespaceBehavior.skip;
38     StringCache cache = StringCache(StringCache.defaultBucketCount);
39     ASTInformation astInformation;
40     RollbackAllocator allocator;
41     auto parseTokens = getTokensForParser(buffer, parseConfig, &cache);
42     auto mod = parseModule(parseTokens, source_desc, &allocator);
43     auto visitor = new FormatVisitor(&astInformation);
44     visitor.visit(mod);
45     astInformation.cleanup();
46     auto tokenRange = byToken(buffer, config, &cache);
47     auto app = appender!(Token[])();
48     for (; !tokenRange.empty(); tokenRange.popFront())
49         app.put(tokenRange.front());
50     auto tokens = app.data;
51     if (!tokenRange.messages.empty)
52         return false;
53     auto depths = generateDepthInfo(tokens);
54     auto tokenFormatter = TokenFormatter!OutputRange(buffer, tokens, depths,
55             output, &astInformation, formatterConfig);
56     tokenFormatter.format();
57     return true;
58 }
59 
60 immutable(short[]) generateDepthInfo(const Token[] tokens) pure nothrow @trusted
61 {
62     import std.exception : assumeUnique;
63 
64     short[] retVal = new short[](tokens.length);
65     short depth = 0;
66     foreach (i, ref t; tokens)
67     {
68         switch (t.type)
69         {
70         case tok!"[":
71             depth++;
72             goto case;
73         case tok!"{":
74         case tok!"(":
75             depth++;
76             break;
77         case tok!"]":
78             depth--;
79             goto case;
80         case tok!"}":
81         case tok!")":
82             depth--;
83             break;
84         default:
85             break;
86         }
87         retVal[i] = depth;
88     }
89     return cast(immutable) retVal;
90 }
91 
92 struct TokenFormatter(OutputRange)
93 {
94     /**
95      * Params:
96      *     rawSource = ?
97      *     tokens = the tokens to format
98      *     depths = ?
99      *     output = the output range that the code will be formatted to
100      *     astInformation = information about the AST used to inform formatting
101      *         decisions.
102      *     config = ?
103      */
104     this(const ubyte[] rawSource, const(Token)[] tokens, immutable short[] depths,
105             OutputRange output, ASTInformation* astInformation, Config* config)
106     {
107         this.rawSource = rawSource;
108         this.tokens = tokens;
109         this.depths = depths;
110         this.output = output;
111         this.astInformation = astInformation;
112         this.config = config;
113 
114         {
115             auto eol = config.end_of_line;
116             if (eol == eol.cr)
117                 this.eolString = "\r";
118             else if (eol == eol.lf)
119                 this.eolString = "\n";
120             else if (eol == eol.crlf)
121                 this.eolString = "\r\n";
122             else if (eol == eol.unspecified)
123                 assert(false, "config.end_of_line was unspecified");
124         }
125     }
126 
127     /// Runs the formatting process
128     void format()
129     {
130         while (hasCurrent)
131             formatStep();
132     }
133 
134 private:
135 
136     /// Current indentation level
137     int indentLevel;
138 
139     /// Current index into the tokens array
140     size_t index;
141 
142     /// Length of the current line (so far)
143     uint currentLineLength = 0;
144 
145     /// Output to write output to
146     OutputRange output;
147 
148     /// Used for skipping parts of the file with `dfmt off` and `dfmt on` comments
149     const ubyte[] rawSource;
150 
151     /// Tokens being formatted
152     const Token[] tokens;
153 
154     /// Paren depth info
155     immutable short[] depths;
156 
157     /// Information about the AST
158     const ASTInformation* astInformation;
159 
160     /// Token indices where line breaks should be placed
161     size_t[] linebreakHints;
162 
163     /// Current indentation stack for the file
164     IndentStack indents;
165 
166     /// Configuration
167     const Config* config;
168 
169     /// chached end of line string
170     const string eolString;
171 
172     /// Keep track of whether or not an extra newline was just added because of
173     /// an import statement.
174     bool justAddedExtraNewline;
175 
176     /// Current paren depth
177     int parenDepth;
178 
179     /// Current special brace depth. Used for struct initializers and lambdas.
180     int sBraceDepth;
181 
182     /// Current non-indented brace depth. Used for struct initializers and lambdas.
183     int niBraceDepth;
184 
185     /// True if a space should be placed when parenDepth reaches zero
186     bool spaceAfterParens;
187 
188     /// True if we're in an ASM block
189     bool inAsm;
190 
191     /// True if the next "this" should have a space behind it
192     bool thisSpace;
193 
194     /// True if the next "else" should be formatted as a single line
195     bool inlineElse;
196 
197     void formatStep()
198     {
199         import std.range : assumeSorted;
200 
201         assert(hasCurrent);
202         if (currentIs(tok!"comment"))
203         {
204             formatComment();
205         }
206         else if (isStringLiteral(current.type)
207                 || isNumberLiteral(current.type) || currentIs(tok!"characterLiteral"))
208         {
209             writeToken();
210             if (hasCurrent)
211             {
212                 immutable t = tokens[index].type;
213                 if (t == tok!"identifier" || isStringLiteral(t)
214                         || isNumberLiteral(t) || t == tok!"characterLiteral"
215                         // a!"b" function()
216                         || t == tok!"function" || t == tok!"delegate")
217                     write(" ");
218             }
219         }
220         else if (currentIs(tok!"module") || currentIs(tok!"import"))
221         {
222             formatModuleOrImport();
223         }
224         else if (currentIs(tok!"return"))
225         {
226             writeToken();
227             if (!currentIs(tok!";") && !currentIs(tok!")") && !currentIs(tok!"{")
228                     && !currentIs(tok!"in") && !currentIs(tok!"out") && !currentIs(tok!"do")
229                     && (hasCurrent && tokens[index].text != "body"))
230                 write(" ");
231         }
232         else if (currentIs(tok!"with"))
233         {
234             if (indents.length == 0 || !indents.topIsOneOf(tok!"switch", tok!"with"))
235                 indents.push(tok!"with");
236             writeToken();
237             write(" ");
238             if (currentIs(tok!"("))
239                 writeParens(false);
240             if (!currentIs(tok!"switch") && !currentIs(tok!"with")
241                     && !currentIs(tok!"{") && !(currentIs(tok!"final") && peekIs(tok!"switch")))
242             {
243                 newline();
244             }
245             else if (!currentIs(tok!"{"))
246                 write(" ");
247         }
248         else if (currentIs(tok!"switch"))
249         {
250             formatSwitch();
251         }
252         else if (currentIs(tok!"extern") && peekIs(tok!"("))
253         {
254             writeToken();
255             write(" ");
256             while (hasCurrent)
257             {
258                 if (currentIs(tok!"("))
259                     formatLeftParenOrBracket();
260                 else if (currentIs(tok!")"))
261                 {
262                     formatRightParen();
263                     break;
264                 }
265                 else
266                     writeToken();
267             }
268         }
269         else if (((isBlockHeader() || currentIs(tok!"version")) && peekIs(tok!"("))
270                 || (currentIs(tok!"debug") && peekIs(tok!"{")))
271         {
272             if (!assumeSorted(astInformation.constraintLocations).equalRange(current.index).empty)
273                 formatConstraint();
274             else
275                 formatBlockHeader();
276         }
277         else if ((current.text == "body" || current == tok!"do") && peekBackIsFunctionDeclarationEnding())
278         {
279             formatKeyword();
280         }
281         else if (currentIs(tok!"do"))
282         {
283             formatBlockHeader();
284         }
285         else if (currentIs(tok!"else"))
286         {
287             formatElse();
288         }
289         else if (currentIs(tok!"asm"))
290         {
291             formatKeyword();
292             while (hasCurrent && !currentIs(tok!"{"))
293                 formatStep();
294             if (hasCurrent)
295             {
296                 int depth = 1;
297                 formatStep();
298                 inAsm = true;
299                 while (hasCurrent && depth > 0)
300                 {
301                     if (currentIs(tok!"{"))
302                         ++depth;
303                     else if (currentIs(tok!"}"))
304                         --depth;
305                     formatStep();
306                 }
307                 inAsm = false;
308             }
309         }
310         else if (currentIs(tok!"this"))
311         {
312             const thisIndex = current.index;
313             formatKeyword();
314             if (config.dfmt_space_before_function_parameters
315                 && (thisSpace || astInformation.constructorDestructorLocations
316                     .canFindIndex(thisIndex)))
317             {
318                 write(" ");
319                 thisSpace = false;
320             }
321         }
322         else if (isKeyword(current.type))
323         {
324             if (currentIs(tok!"debug"))
325                 inlineElse = true;
326             formatKeyword();
327         }
328         else if (isBasicType(current.type))
329         {
330             writeToken();
331             if (currentIs(tok!"identifier") || isKeyword(current.type) || inAsm)
332                 write(" ");
333         }
334         else if (isOperator(current.type))
335         {
336             formatOperator();
337         }
338         else if (currentIs(tok!"identifier"))
339         {
340             writeToken();
341             //dfmt off
342             if (hasCurrent && ( currentIs(tok!"identifier")
343                     || ( index > 1 && config.dfmt_space_before_function_parameters
344                         && ( isBasicType(peekBack(2).type)
345                             || peekBack2Is(tok!"identifier")
346                             || peekBack2Is(tok!")")
347                             || peekBack2Is(tok!"]") )
348                         && currentIs(tok!("(") )
349                     || isBasicType(current.type) || currentIs(tok!"@")
350                     || isNumberLiteral(tokens[index].type)
351                     || (inAsm  && peekBack2Is(tok!";") && currentIs(tok!"["))
352             )))
353             //dfmt on
354             {
355                 write(" ");
356             }
357         }
358         else if (currentIs(tok!"scriptLine") || currentIs(tok!"specialTokenSequence"))
359         {
360             writeToken();
361             newline();
362         }
363         else
364             writeToken();
365     }
366 
367     void formatConstraint()
368     {
369         import dfmt.editorconfig : OB = OptionalBoolean;
370         with (TemplateConstraintStyle) final switch (config.dfmt_template_constraint_style)
371         {
372         case unspecified:
373             assert(false, "Config was not validated properly");
374         case conditional_newline:
375             immutable l = currentLineLength + betweenParenLength(tokens[index + 1 .. $]);
376             if (l > config.dfmt_soft_max_line_length)
377                 newline();
378             else if (peekBackIs(tok!")") || peekBackIs(tok!"identifier"))
379                 write(" ");
380             break;
381         case always_newline:
382             newline();
383             break;
384         case conditional_newline_indent:
385             immutable l = currentLineLength + betweenParenLength(tokens[index + 1 .. $]);
386             if (l > config.dfmt_soft_max_line_length)
387             {
388                 config.dfmt_single_template_constraint_indent == OB.t ?
389                     pushWrapIndent() : pushWrapIndent(tok!"!");
390                 newline();
391             }
392             else if (peekBackIs(tok!")") || peekBackIs(tok!"identifier"))
393                 write(" ");
394             break;
395         case always_newline_indent:
396             {
397                 config.dfmt_single_template_constraint_indent == OB.t ?
398                     pushWrapIndent() : pushWrapIndent(tok!"!");
399                 newline();
400             }
401             break;
402         }
403         // if
404         writeToken();
405         // assume that the parens are present, otherwise the parser would not
406         // have told us there was a constraint here
407         write(" ");
408         writeParens(false);
409     }
410 
411     string commentText(size_t i)
412     {
413         import std..string : strip;
414 
415         assert(tokens[i].type == tok!"comment");
416         string commentText = tokens[i].text;
417         if (commentText[0 .. 2] == "//")
418             commentText = commentText[2 .. $];
419         else
420         {
421             if (commentText.length > 3)
422                 commentText = commentText[2 .. $ - 2];
423             else
424                 commentText = commentText[2 .. $];
425         }
426         return commentText.strip();
427     }
428 
429     void skipFormatting()
430     {
431         size_t dfmtOff = index;
432         size_t dfmtOn = index;
433         foreach (i; dfmtOff + 1 .. tokens.length)
434         {
435             dfmtOn = i;
436             if (tokens[i].type != tok!"comment")
437                 continue;
438             immutable string commentText = commentText(i);
439             if (commentText == "dfmt on")
440                 break;
441         }
442         write(cast(string) rawSource[tokens[dfmtOff].index .. tokens[dfmtOn].index]);
443         index = dfmtOn;
444     }
445 
446     void formatComment()
447     {
448         if (commentText(index) == "dfmt off")
449         {
450             skipFormatting();
451             return;
452         }
453 
454         immutable bool currIsSlashSlash = tokens[index].text[0 .. 2] == "//";
455         immutable prevTokenEndLine = index == 0 ? size_t.max : tokenEndLine(tokens[index - 1]);
456         immutable size_t currTokenLine = tokens[index].line;
457         if (index > 0)
458         {
459             immutable t = tokens[index - 1].type;
460             immutable canAddNewline = currTokenLine - prevTokenEndLine < 1;
461             if (peekBackIsOperator() && !isSeparationToken(t))
462                 pushWrapIndent(t);
463             else if (peekBackIs(tok!",") && prevTokenEndLine == currTokenLine
464                     && indents.indentToMostRecent(tok!"enum") == -1)
465                 pushWrapIndent(tok!",");
466             if (peekBackIsOperator() && !peekBackIsOneOf(false, tok!"comment",
467                     tok!"{", tok!"}", tok!":", tok!";", tok!",", tok!"[", tok!"(")
468                     && !canAddNewline && prevTokenEndLine < currTokenLine)
469                 write(" ");
470             else if (prevTokenEndLine == currTokenLine || (t == tok!")" && peekIs(tok!"{")))
471                 write(" ");
472             else if (peekBackIsOneOf(false, tok!"else", tok!"identifier"))
473                 write(" ");
474             else if (canAddNewline || (peekIs(tok!"{") && t == tok!"}"))
475                 newline();
476 
477             if (peekIs(tok!"(") && (peekBackIs(tok!")") || peekBack2Is(tok!"!")))
478                 pushWrapIndent(tok!"(");
479         }
480         writeToken();
481         immutable j = justAddedExtraNewline;
482         if (currIsSlashSlash)
483         {
484             newline();
485             justAddedExtraNewline = j;
486         }
487         else if (hasCurrent)
488         {
489             if (prevTokenEndLine == tokens[index].line)
490             {
491                 if (currentIs(tok!"}"))
492                 {
493                     if (indents.topIs(tok!"{"))
494                         indents.pop();
495                     write(" ");
496                 }
497                 else if (!currentIs(tok!"{"))
498                     write(" ");
499             }
500             else if (!currentIs(tok!"{") && !currentIs(tok!"in") && !currentIs(tok!"out"))
501             {
502                 if (currentIs(tok!")") && indents.topIs(tok!","))
503                     indents.pop();
504                 else if (peekBack2Is(tok!",") && !indents.topIs(tok!",")
505                         && indents.indentToMostRecent(tok!"enum") == -1)
506                     pushWrapIndent(tok!",");
507                 newline();
508             }
509         }
510         else
511             newline();
512     }
513 
514     void formatModuleOrImport()
515     {
516         immutable t = current.type;
517         writeToken();
518         if (currentIs(tok!"("))
519         {
520             writeParens(false);
521             return;
522         }
523         write(" ");
524         while (hasCurrent)
525         {
526             if (currentIs(tok!";"))
527             {
528                 indents.popWrapIndents();
529                 indentLevel = indents.indentLevel;
530                 writeToken();
531                 if (index >= tokens.length)
532                 {
533                     newline();
534                     break;
535                 }
536                 if (currentIs(tok!"comment") && current.line == peekBack().line)
537                 {
538                     break;
539                 }
540                 else if (currentIs(tok!"{") && config.dfmt_brace_style == BraceStyle.allman)
541                     break;
542                 else if (t == tok!"import" && !currentIs(tok!"import")
543                         && !currentIs(tok!"}")
544                             && !((currentIs(tok!"public")
545                                 || currentIs(tok!"private")
546                                 || currentIs(tok!"static"))
547                             && peekIs(tok!"import")) && !indents.topIsOneOf(tok!"if",
548                             tok!"debug", tok!"version"))
549                 {
550                     simpleNewline();
551                     currentLineLength = 0;
552                     justAddedExtraNewline = true;
553                     newline();
554                 }
555                 else
556                     newline();
557                 break;
558             }
559             else if (currentIs(tok!":"))
560             {
561                 if (config.dfmt_selective_import_space)
562                     write(" ");
563                 writeToken();
564                 if (!currentIs(tok!"comment"))
565                     write(" ");
566                 pushWrapIndent(tok!",");
567             }
568             else if (currentIs(tok!"comment"))
569             {
570                 if (peekBack.line != current.line)
571                 {
572                     // The comment appears on its own line, keep it there.
573                     if (!peekBackIs(tok!"comment"))
574                         // Comments are already properly separated.
575                         newline();
576                 }
577                 formatStep();
578             }
579             else
580                 formatStep();
581         }
582     }
583 
584     void formatLeftParenOrBracket()
585     in
586     {
587         assert(currentIs(tok!"(") || currentIs(tok!"["));
588     }
589     do
590     {
591         import dfmt.editorconfig : OptionalBoolean;
592 
593         immutable p = current.type;
594         regenLineBreakHintsIfNecessary(index);
595         writeToken();
596         if (p == tok!"(")
597         {
598             // If the file starts with an open paren, just give up. This isn't
599             // valid D code.
600             if (index < 2)
601                 return;
602             if (isBlockHeaderToken(tokens[index - 2].type))
603                 indents.push(tok!")");
604             else
605                 indents.push(p);
606             spaceAfterParens = true;
607             parenDepth++;
608         }
609         // No heuristics apply if we can't look before the opening paren/bracket
610         if (index < 1)
611             return;
612         immutable bool arrayInitializerStart = p == tok!"["
613             && astInformation.arrayStartLocations.canFindIndex(tokens[index - 1].index);
614 
615         if (arrayInitializerStart && isMultilineAt(index - 1))
616         {
617             if (peekBack2Is(tok!"(")) {
618                 indents.pop();
619             }
620 
621             // Use the close bracket as the indent token to distinguish
622             // the array initialiazer from an array index in the newline
623             // handling code
624             IndentStack.Details detail;
625             detail.wrap = false;
626             detail.temp = false;
627 
628             // wrap and temp are set manually to the values it would actually
629             // receive here because we want to set breakEveryItem for the ] token to know if
630             // we should definitely always new-line after every comma for a big AA
631             detail.breakEveryItem = astInformation.assocArrayStartLocations.canFindIndex(
632                     tokens[index - 1].index);
633             detail.preferLongBreaking = true;
634 
635             indents.push(tok!"]", detail);
636             newline();
637             immutable size_t j = expressionEndIndex(index);
638             linebreakHints = chooseLineBreakTokens(index, tokens[index .. j],
639                     depths[index .. j], config, currentLineLength, indentLevel);
640         }
641         else if (p == tok!"[" && config.dfmt_keep_line_breaks == OptionalBoolean.t)
642         {
643             IndentStack.Details detail;
644 
645             detail.wrap = false;
646             detail.temp = false;
647             detail.breakEveryItem = false;
648             detail.mini = tokens[index].line == tokens[index - 1].line;
649 
650             indents.push(tok!"]", detail);
651             if (!detail.mini)
652             {
653                 newline();
654             }
655         }
656         else if (arrayInitializerStart)
657         {
658             // This is a short (non-breaking) array/AA value
659             IndentStack.Details detail;
660             detail.wrap = false;
661             detail.temp = false;
662 
663             detail.breakEveryItem = astInformation.assocArrayStartLocations.canFindIndex(tokens[index - 1].index);
664             // array of (possibly associative) array, let's put each item on its own line
665             if (!detail.breakEveryItem && currentIs(tok!"["))
666                 detail.breakEveryItem = true;
667 
668             // the '[' is immediately followed by an item instead of a newline here so
669             // we set mini, that the ']' also follows an item immediately without newline.
670             detail.mini = true;
671 
672             indents.push(tok!"]", detail);
673         }
674         else if (p == tok!"[")
675         {
676             // array item access
677             IndentStack.Details detail;
678             detail.wrap = false;
679             detail.temp = true;
680             detail.mini = true;
681             indents.push(tok!"]", detail);
682         }
683         else if (!currentIs(tok!")") && !currentIs(tok!"]")
684                 && (linebreakHints.canFindIndex(index - 1) || (linebreakHints.length == 0
685                     && currentLineLength > config.max_line_length)))
686         {
687             newline();
688         }
689         else if (onNextLine)
690         {
691             newline();
692         }
693     }
694 
695     void formatRightParen()
696     in
697     {
698         assert(currentIs(tok!")"));
699     }
700     do
701     {
702         parenDepth--;
703         indents.popWrapIndents();
704         while (indents.topIsOneOf(tok!"!", tok!")"))
705             indents.pop();
706         if (indents.topIs(tok!"("))
707             indents.pop();
708 
709         if (onNextLine)
710         {
711             newline();
712         }
713         if (parenDepth == 0 && (peekIs(tok!"is") || peekIs(tok!"in")
714             || peekIs(tok!"out") || peekIs(tok!"do") || peekIsBody))
715         {
716             writeToken();
717         }
718         else if (peekIsLiteralOrIdent() || peekIsBasicType())
719         {
720             writeToken();
721             if (spaceAfterParens || parenDepth > 0)
722                 writeSpace();
723         }
724         else if ((peekIsKeyword() || peekIs(tok!"@")) && spaceAfterParens
725                 && !peekIs(tok!"in") && !peekIs(tok!"is") && !peekIs(tok!"if"))
726         {
727             writeToken();
728             writeSpace();
729         }
730         else
731             writeToken();
732     }
733 
734     void formatRightBracket()
735     in
736     {
737         assert(currentIs(tok!"]"));
738     }
739     do
740     {
741         indents.popWrapIndents();
742         if (indents.topIs(tok!"]"))
743         {
744             if (!indents.topDetails.mini && !indents.topDetails.temp)
745                 newline();
746             else
747                 indents.pop();
748         }
749         writeToken();
750         if (currentIs(tok!"identifier"))
751             write(" ");
752     }
753 
754     void formatAt()
755     {
756         immutable size_t atIndex = tokens[index].index;
757         writeToken();
758         if (currentIs(tok!"identifier"))
759             writeToken();
760         if (currentIs(tok!"("))
761         {
762             writeParens(false);
763             if (tokens[index].type == tok!"{")
764                 return;
765 
766             if (hasCurrent && tokens[index - 1].line < tokens[index].line
767                     && astInformation.atAttributeStartLocations.canFindIndex(atIndex))
768                 newline();
769             else
770                 write(" ");
771         }
772         else if (hasCurrent && (currentIs(tok!"@")
773                 || isBasicType(tokens[index].type)
774                 || currentIs(tok!"invariant")
775                 || currentIs(tok!"extern")
776                 || currentIs(tok!"identifier"))
777                 && !currentIsIndentedTemplateConstraint())
778         {
779             writeSpace();
780         }
781     }
782 
783     void formatColon()
784     {
785         import dfmt.editorconfig : OptionalBoolean;
786         import std.algorithm : canFind, any;
787 
788         immutable bool isCase = astInformation.caseEndLocations.canFindIndex(current.index);
789         immutable bool isAttribute = astInformation.attributeDeclarationLines.canFindIndex(
790                 current.line);
791         immutable bool isStructInitializer = astInformation.structInfoSortedByEndLocation
792             .canFind!(st => st.startLocation < current.index && current.index < st.endLocation);
793 
794         if (isCase || isAttribute)
795         {
796             writeToken();
797             if (!currentIs(tok!"{"))
798             {
799                 if (isCase && !indents.topIs(tok!"case")
800                         && config.dfmt_align_switch_statements == OptionalBoolean.f)
801                     indents.push(tok!"case");
802                 else if (isAttribute && !indents.topIs(tok!"@")
803                         && config.dfmt_outdent_attributes == OptionalBoolean.f)
804                     indents.push(tok!"@");
805                 newline();
806             }
807         }
808         else if (indents.topIs(tok!"]")) // Associative array
809         {
810             write(config.dfmt_space_before_aa_colon ? " : " : ": ");
811             ++index;
812         }
813         else if (peekBackIs(tok!"identifier")
814                 && [tok!"{", tok!"}", tok!";", tok!":", tok!","]
815                 .any!((ptrdiff_t token) => peekBack2Is(cast(IdType)token, true))
816                 && (!isBlockHeader(1) || peekIs(tok!"if")))
817         {
818             writeToken();
819             if (isStructInitializer)
820                 write(" ");
821             else if (!currentIs(tok!"{"))
822                 newline();
823         }
824         else
825         {
826             regenLineBreakHintsIfNecessary(index);
827             if (peekIs(tok!".."))
828                 writeToken();
829             else if (isBlockHeader(1) && !peekIs(tok!"if"))
830             {
831                 writeToken();
832                 if (config.dfmt_compact_labeled_statements)
833                     write(" ");
834                 else
835                     newline();
836             }
837             else if (linebreakHints.canFindIndex(index))
838             {
839                 pushWrapIndent();
840                 newline();
841                 writeToken();
842                 write(" ");
843             }
844             else
845             {
846                 write(" : ");
847                 index++;
848             }
849         }
850     }
851 
852     void formatSemicolon()
853     {
854         if (inlineElse && !peekIs(tok!"else"))
855             inlineElse = false;
856 
857         if ((parenDepth > 0 && sBraceDepth == 0) || (sBraceDepth > 0 && niBraceDepth > 0))
858         {
859             if (currentLineLength > config.dfmt_soft_max_line_length)
860             {
861                 writeToken();
862                 pushWrapIndent(tok!";");
863                 newline();
864             }
865             else
866             {
867                 if (!(peekIs(tok!";") || peekIs(tok!")") || peekIs(tok!"}")))
868                     write("; ");
869                 else
870                     write(";");
871                 index++;
872             }
873         }
874         else
875         {
876             writeToken();
877             indents.popWrapIndents();
878             linebreakHints = [];
879             while (indents.topIsOneOf(tok!"enum", tok!"try", tok!"catch", tok!"finally", tok!"debug"))
880                 indents.pop();
881             if (indents.topAre(tok!"static", tok!"else"))
882             {
883                 indents.pop();
884                 indents.pop();
885             }
886             indentLevel = indents.indentLevel;
887             if (config.dfmt_brace_style == BraceStyle.allman)
888             {
889                 if (!currentIs(tok!"{"))
890                     newline();
891             }
892             else
893             {
894                 if (currentIs(tok!"{"))
895                     indents.popTempIndents();
896                 indentLevel = indents.indentLevel;
897                 newline();
898             }
899         }
900     }
901 
902     void formatLeftBrace()
903     {
904         import std.algorithm : map, sum, canFind;
905 
906         auto tIndex = tokens[index].index;
907 
908         if (astInformation.structInitStartLocations.canFindIndex(tIndex))
909         {
910             sBraceDepth++;
911             immutable bool multiline = isMultilineAt(index);
912             writeToken();
913             if (multiline)
914             {
915                 import std.algorithm.searching : find;
916 
917                 auto indentInfo = astInformation.indentInfoSortedByEndLocation
918                     .find!((a,b) => a.startLocation == b)(tIndex);
919                 assert(indentInfo.length > 0);
920                 cast()indentInfo[0].flags |= BraceIndentInfoFlags.tempIndent;
921                 cast()indentInfo[0].beginIndentLevel = indents.indentLevel;
922 
923                 indents.push(tok!"{");
924                 newline();
925             }
926             else
927                 niBraceDepth++;
928         }
929         else if (astInformation.funLitStartLocations.canFindIndex(tIndex))
930         {
931             indents.popWrapIndents();
932 
933             sBraceDepth++;
934             if (peekBackIsOneOf(true, tok!")", tok!"identifier"))
935                 write(" ");
936             immutable bool multiline = isMultilineAt(index);
937             writeToken();
938             if (multiline)
939             {
940                 indents.push(tok!"{");
941                 newline();
942             }
943             else
944             {
945                 niBraceDepth++;
946                 if (!currentIs(tok!"}"))
947                     write(" ");
948             }
949         }
950         else
951         {
952             if (peekBackIsSlashSlash())
953             {
954                 if (peekBack2Is(tok!";"))
955                 {
956                     indents.popTempIndents();
957                     indentLevel = indents.indentLevel - 1;
958                 }
959                 writeToken();
960             }
961             else
962             {
963                 if (indents.topIsTemp && indents.indentToMostRecent(tok!"static") == -1)
964                     indentLevel = indents.indentLevel - 1;
965                 else
966                     indentLevel = indents.indentLevel;
967                 if (config.dfmt_brace_style == BraceStyle.allman
968                         || peekBackIsOneOf(true, tok!"{", tok!"}"))
969                     newline();
970                 else if (!peekBackIsOneOf(true, tok!"{", tok!"}", tok!";"))
971                     write(" ");
972                 writeToken();
973             }
974             indents.push(tok!"{");
975             if (!currentIs(tok!"{"))
976                 newline();
977             linebreakHints = [];
978         }
979     }
980 
981     void formatRightBrace()
982     {
983         void popToBeginIndent(BraceIndentInfo indentInfo)
984         {
985             foreach(i; indentInfo.beginIndentLevel .. indents.indentLevel)
986             {
987                 indents.pop();
988             }
989 
990             indentLevel = indentInfo.beginIndentLevel;
991         }
992 
993         size_t pos;
994         if (astInformation.structInitEndLocations.canFindIndex(tokens[index].index, &pos))
995         {
996             if (sBraceDepth > 0)
997                 sBraceDepth--;
998             if (niBraceDepth > 0)
999                 niBraceDepth--;
1000 
1001             auto indentInfo = astInformation.indentInfoSortedByEndLocation[pos];
1002             if (indentInfo.flags & BraceIndentInfoFlags.tempIndent)
1003             {
1004                 popToBeginIndent(indentInfo);
1005                 simpleNewline();
1006                 indent();
1007             }
1008             writeToken();
1009         }
1010         else if (astInformation.funLitEndLocations.canFindIndex(tokens[index].index, &pos))
1011         {
1012             if (niBraceDepth > 0)
1013             {
1014                 if (!peekBackIsSlashSlash() && !peekBackIs(tok!"{"))
1015                     write(" ");
1016                 niBraceDepth--;
1017             }
1018             if (sBraceDepth > 0)
1019                 sBraceDepth--;
1020             writeToken();
1021         }
1022         else
1023         {
1024             // Silly hack to format enums better.
1025             if ((peekBackIsLiteralOrIdent() || peekBackIsOneOf(true, tok!")",
1026                     tok!",")) && !peekBackIsSlashSlash())
1027                 newline();
1028             write("}");
1029             if (index + 1 < tokens.length
1030                     && astInformation.doubleNewlineLocations.canFindIndex(tokens[index].index)
1031                     && !peekIs(tok!"}") && !peekIs(tok!"else")
1032                     && !peekIs(tok!";") && !peekIs(tok!"comment", false))
1033             {
1034                 simpleNewline();
1035                 currentLineLength = 0;
1036                 justAddedExtraNewline = true;
1037             }
1038             if (config.dfmt_brace_style == BraceStyle.otbs
1039                     && ((peekIs(tok!"else")
1040                             && !indents.topAre(tok!"static", tok!"if")
1041                             && !indents.topIs(tok!"foreach") && !indents.topIs(tok!"for")
1042                             && !indents.topIs(tok!"while") && !indents.topIs(tok!"do"))
1043                         || peekIs(tok!"catch") || peekIs(tok!"finally")))
1044             {
1045                 write(" ");
1046                 index++;
1047             }
1048             else
1049             {
1050                 if (!peekIs(tok!",") && !peekIs(tok!")")
1051                         && !peekIs(tok!";") && !peekIs(tok!"{"))
1052                 {
1053                     index++;
1054                     if (indents.topIs(tok!"static"))
1055                         indents.pop();
1056                     newline();
1057                 }
1058                 else
1059                     index++;
1060             }
1061         }
1062     }
1063 
1064     void formatSwitch()
1065     {
1066         while (indents.topIs(tok!"with"))
1067             indents.pop();
1068         indents.push(tok!"switch");
1069         writeToken(); // switch
1070         write(" ");
1071     }
1072 
1073     void formatBlockHeader()
1074     {
1075         if (indents.topIs(tok!"!"))
1076             indents.pop();
1077         immutable bool a = !currentIs(tok!"version") && !currentIs(tok!"debug");
1078         immutable bool b = a
1079             || astInformation.conditionalWithElseLocations.canFindIndex(current.index);
1080         immutable bool c = b
1081             || astInformation.conditionalStatementLocations.canFindIndex(current.index);
1082         immutable bool shouldPushIndent = (c || peekBackIs(tok!"else"))
1083             && !(currentIs(tok!"if") && indents.topIsWrap());
1084         if (currentIs(tok!"out") && !peekBackIs(tok!"}"))
1085             newline();
1086         if (shouldPushIndent)
1087         {
1088             if (peekBackIs(tok!"static"))
1089             {
1090                 if (indents.topIs(tok!"else"))
1091                     indents.pop();
1092                 if (!indents.topIs(tok!"static"))
1093                     indents.push(tok!"static");
1094             }
1095             indents.push(current.type);
1096         }
1097         writeToken();
1098 
1099         if (currentIs(tok!"("))
1100         {
1101             write(" ");
1102             writeParens(false);
1103         }
1104 
1105         if (hasCurrent)
1106         {
1107             if (currentIs(tok!"switch") || (currentIs(tok!"final") && peekIs(tok!"switch")))
1108                 write(" ");
1109             else if (currentIs(tok!"comment"))
1110                 formatStep();
1111             else if (!shouldPushIndent)
1112             {
1113                 if (!currentIs(tok!"{") && !currentIs(tok!";"))
1114                     write(" ");
1115             }
1116             else if (hasCurrent && !currentIs(tok!"{") && !currentIs(tok!";") && !currentIs(tok!"in") &&
1117                 !currentIs(tok!"out") && !currentIs(tok!"do") && current.text != "body")
1118             {
1119                 newline();
1120             }
1121             else if (currentIs(tok!"{") && indents.topAre(tok!"static", tok!"if"))
1122             {
1123                 // Hacks to format braced vs non-braced static if declarations.
1124                 indents.pop();
1125                 indents.pop();
1126                 indents.push(tok!"if");
1127                 formatLeftBrace();
1128             }
1129             else if (currentIs(tok!"{") && indents.topAre(tok!"static", tok!"foreach"))
1130             {
1131                 indents.pop();
1132                 indents.pop();
1133                 indents.push(tok!"foreach");
1134                 formatLeftBrace();
1135             }
1136             else if (currentIs(tok!"{") && indents.topAre(tok!"static", tok!"foreach_reverse"))
1137             {
1138                 indents.pop();
1139                 indents.pop();
1140                 indents.push(tok!"foreach_reverse");
1141                 formatLeftBrace();
1142             }
1143         }
1144     }
1145 
1146     void formatElse()
1147     {
1148         writeToken();
1149         if (inlineElse || currentIs(tok!"if") || currentIs(tok!"version")
1150                 || (currentIs(tok!"static") && peekIs(tok!"if")))
1151         {
1152             if (indents.topIs(tok!"if") || indents.topIs(tok!"version"))
1153                 indents.pop();
1154             inlineElse = false;
1155             write(" ");
1156         }
1157         else if (currentIs(tok!":"))
1158         {
1159             writeToken();
1160             newline();
1161         }
1162         else if (!currentIs(tok!"{") && !currentIs(tok!"comment"))
1163         {
1164             //indents.dump();
1165             while (indents.topIsOneOf(tok!"foreach", tok!"for", tok!"while"))
1166                 indents.pop();
1167             if (indents.topIsOneOf(tok!"if", tok!"version"))
1168                 indents.pop();
1169             indents.push(tok!"else");
1170             newline();
1171         }
1172         else if (currentIs(tok!"{") && indents.topAre(tok!"static", tok!"if"))
1173         {
1174             indents.pop();
1175             indents.pop();
1176             indents.push(tok!"else");
1177         }
1178     }
1179 
1180     void formatKeyword()
1181     {
1182         import dfmt.editorconfig : OptionalBoolean;
1183 
1184         switch (current.type)
1185         {
1186         case tok!"default":
1187             writeToken();
1188             break;
1189         case tok!"cast":
1190             writeToken();
1191             if (currentIs(tok!"("))
1192                 writeParens(config.dfmt_space_after_cast == OptionalBoolean.t);
1193             break;
1194         case tok!"out":
1195             if (!peekBackIsSlashSlash) {
1196                 if (!peekBackIs(tok!"}")
1197                         && astInformation.contractLocations.canFindIndex(current.index))
1198                     newline();
1199                 else if (peekBackIsKeyword)
1200                     write(" ");
1201             }
1202             writeToken();
1203             if (!currentIs(tok!"{") && !currentIs(tok!"comment"))
1204                 write(" ");
1205             break;
1206         case tok!"try":
1207         case tok!"finally":
1208             indents.push(current.type);
1209             writeToken();
1210             if (!currentIs(tok!"{"))
1211                 newline();
1212             break;
1213         case tok!"identifier":
1214             if (current.text == "body")
1215                 goto case tok!"do";
1216             else
1217                 goto default;
1218         case tok!"do":
1219             if (!peekBackIs(tok!"}"))
1220                 newline();
1221             writeToken();
1222             break;
1223         case tok!"in":
1224             immutable isContract = astInformation.contractLocations.canFindIndex(current.index);
1225             if (!peekBackIsSlashSlash) {
1226                 if (isContract)
1227                 {
1228                     indents.popTempIndents();
1229                     newline();
1230                 }
1231                 else if (!peekBackIsOneOf(false, tok!"(", tok!",", tok!"!"))
1232                     write(" ");
1233             }
1234             writeToken();
1235             immutable isFunctionLit = astInformation.funLitStartLocations.canFindIndex(
1236                     current.index);
1237             if (isFunctionLit && config.dfmt_brace_style == BraceStyle.allman)
1238                 newline();
1239             else if (!isContract || currentIs(tok!"("))
1240                 write(" ");
1241             break;
1242         case tok!"is":
1243             if (!peekBackIsOneOf(false, tok!"!", tok!"(", tok!",",
1244                     tok!"}", tok!"=", tok!"&&", tok!"||") && !peekBackIsKeyword())
1245                 write(" ");
1246             writeToken();
1247             if (!currentIs(tok!"(") && !currentIs(tok!"{") && !currentIs(tok!"comment"))
1248                 write(" ");
1249             break;
1250         case tok!"case":
1251             writeToken();
1252             if (!currentIs(tok!";"))
1253                 write(" ");
1254             break;
1255         case tok!"enum":
1256             if (peekIs(tok!")") || peekIs(tok!"=="))
1257             {
1258                 writeToken();
1259             }
1260             else
1261             {
1262                 if (peekBackIs(tok!"identifier"))
1263                     write(" ");
1264                 indents.push(tok!"enum");
1265                 writeToken();
1266                 if (!currentIs(tok!":") && !currentIs(tok!"{"))
1267                     write(" ");
1268             }
1269             break;
1270         case tok!"static":
1271             {
1272                 if (astInformation.staticConstructorDestructorLocations
1273                     .canFindIndex(current.index))
1274                 {
1275                     thisSpace = true;
1276                 }
1277             }
1278             goto default;
1279         case tok!"shared":
1280             {
1281                 if (astInformation.sharedStaticConstructorDestructorLocations
1282                     .canFindIndex(current.index))
1283                 {
1284                     thisSpace = true;
1285                 }
1286             }
1287             goto default;
1288         case tok!"invariant":
1289             writeToken();
1290             if (currentIs(tok!"("))
1291                 write(" ");
1292             break;
1293         default:
1294             if (peekBackIs(tok!"identifier"))
1295             {
1296                 writeSpace();
1297             }
1298             if (index + 1 < tokens.length)
1299             {
1300                 if (!peekIs(tok!"@") && (peekIsOperator()
1301                         || peekIs(tok!"out") || peekIs(tok!"in")))
1302                 {
1303                     writeToken();
1304                 }
1305                 else
1306                 {
1307                     writeToken();
1308                     if (!currentIsIndentedTemplateConstraint())
1309                     {
1310                         writeSpace();
1311                     }
1312                 }
1313             }
1314             else
1315                 writeToken();
1316             break;
1317         }
1318     }
1319 
1320     bool currentIsIndentedTemplateConstraint()
1321     {
1322         return hasCurrent
1323             && astInformation.constraintLocations.canFindIndex(current.index)
1324             && (config.dfmt_template_constraint_style == TemplateConstraintStyle.always_newline
1325                 || config.dfmt_template_constraint_style == TemplateConstraintStyle.always_newline_indent
1326                 || currentLineLength >= config.dfmt_soft_max_line_length);
1327     }
1328 
1329     void formatOperator()
1330     {
1331         import dfmt.editorconfig : OptionalBoolean;
1332         import std.algorithm : canFind;
1333 
1334         switch (current.type)
1335         {
1336         case tok!"*":
1337             if (astInformation.spaceAfterLocations.canFindIndex(current.index))
1338             {
1339                 writeToken();
1340                 if (!currentIs(tok!"*") && !currentIs(tok!")")
1341                         && !currentIs(tok!"[") && !currentIs(tok!",") && !currentIs(tok!";"))
1342                 {
1343                     write(" ");
1344                 }
1345                 break;
1346             }
1347             else if (astInformation.unaryLocations.canFindIndex(current.index))
1348             {
1349                 writeToken();
1350                 break;
1351             }
1352             regenLineBreakHintsIfNecessary(index);
1353             goto binary;
1354         case tok!"~":
1355             if (peekIs(tok!"this") && peek2Is(tok!"("))
1356             {
1357                 if (!(index == 0 || peekBackIs(tok!"{", true)
1358                         || peekBackIs(tok!"}", true) || peekBackIs(tok!";", true)))
1359                 {
1360                     write(" ");
1361                 }
1362                 writeToken();
1363                 break;
1364             }
1365             goto case;
1366         case tok!"&":
1367         case tok!"+":
1368         case tok!"-":
1369             if (astInformation.unaryLocations.canFindIndex(current.index))
1370             {
1371                 writeToken();
1372                 break;
1373             }
1374             regenLineBreakHintsIfNecessary(index);
1375             goto binary;
1376         case tok!"[":
1377         case tok!"(":
1378             formatLeftParenOrBracket();
1379             break;
1380         case tok!")":
1381             formatRightParen();
1382             break;
1383         case tok!"@":
1384             formatAt();
1385             break;
1386         case tok!"!":
1387             if (((peekIs(tok!"is") || peekIs(tok!"in"))
1388                     && !peekBackIsOperator()) || peekBackIs(tok!")"))
1389                 write(" ");
1390             goto case;
1391         case tok!"...":
1392         case tok!"++":
1393         case tok!"--":
1394         case tok!"$":
1395             writeToken();
1396             break;
1397         case tok!":":
1398             formatColon();
1399             break;
1400         case tok!"]":
1401             formatRightBracket();
1402             break;
1403         case tok!";":
1404             formatSemicolon();
1405             break;
1406         case tok!"{":
1407             formatLeftBrace();
1408             break;
1409         case tok!"}":
1410             formatRightBrace();
1411             break;
1412         case tok!".":
1413             regenLineBreakHintsIfNecessary(index);
1414             immutable bool ufcsWrap = astInformation.ufcsHintLocations.canFindIndex(current.index);
1415             if (ufcsWrap || linebreakHints.canFind(index) || onNextLine
1416                     || (linebreakHints.length == 0 && currentLineLength + nextTokenLength() > config.max_line_length))
1417             {
1418                 pushWrapIndent();
1419                 if (!peekBackIs(tok!"comment"))
1420                     newline();
1421                 if (ufcsWrap || onNextLine)
1422                     regenLineBreakHints(index);
1423             }
1424             writeToken();
1425             break;
1426         case tok!",":
1427             formatComma();
1428             break;
1429         case tok!"&&":
1430         case tok!"||":
1431         case tok!"|":
1432             regenLineBreakHintsIfNecessary(index);
1433             goto case;
1434         case tok!"=":
1435         case tok!">=":
1436         case tok!">>=":
1437         case tok!">>>=":
1438         case tok!"|=":
1439         case tok!"-=":
1440         case tok!"/=":
1441         case tok!"*=":
1442         case tok!"&=":
1443         case tok!"%=":
1444         case tok!"+=":
1445         case tok!"^^":
1446         case tok!"^=":
1447         case tok!"^":
1448         case tok!"~=":
1449         case tok!"<<=":
1450         case tok!"<<":
1451         case tok!"<=":
1452         case tok!"<>=":
1453         case tok!"<>":
1454         case tok!"<":
1455         case tok!"==":
1456         case tok!"=>":
1457         case tok!">>>":
1458         case tok!">>":
1459         case tok!">":
1460         case tok!"!<=":
1461         case tok!"!<>=":
1462         case tok!"!<>":
1463         case tok!"!<":
1464         case tok!"!=":
1465         case tok!"!>=":
1466         case tok!"!>":
1467         case tok!"?":
1468         case tok!"/":
1469         case tok!"..":
1470         case tok!"%":
1471         binary:
1472             immutable bool isWrapToken = linebreakHints.canFind(index);
1473             if (config.dfmt_keep_line_breaks == OptionalBoolean.t && index > 0)
1474             {
1475                 const operatorLine = tokens[index].line;
1476                 const rightOperandLine = tokens[index + 1].line;
1477 
1478                 if (tokens[index - 1].line < operatorLine)
1479                 {
1480                     if (!indents.topIs(tok!"enum"))
1481                         pushWrapIndent();
1482                     if (!peekBackIs(tok!"comment"))
1483                         newline();
1484                 }
1485                 else
1486                 {
1487                     write(" ");
1488                 }
1489                 if (rightOperandLine > operatorLine
1490                         && !indents.topIs(tok!"enum"))
1491                 {
1492                     pushWrapIndent();
1493                 }
1494                 writeToken();
1495 
1496                 if (rightOperandLine > operatorLine)
1497                 {
1498                     if (!peekBackIs(tok!"comment"))
1499                         newline();
1500                 }
1501                 else
1502                 {
1503                     write(" ");
1504                 }
1505             }
1506             else if (config.dfmt_split_operator_at_line_end)
1507             {
1508                 if (isWrapToken)
1509                 {
1510                     if (!indents.topIs(tok!"enum"))
1511                         pushWrapIndent();
1512                     write(" ");
1513                     writeToken();
1514                     newline();
1515                 }
1516                 else
1517                 {
1518                     write(" ");
1519                     writeToken();
1520                     if (!currentIs(tok!"comment"))
1521                         write(" ");
1522                 }
1523             }
1524             else
1525             {
1526                 if (isWrapToken)
1527                 {
1528                     if (!indents.topIs(tok!"enum"))
1529                         pushWrapIndent();
1530                     newline();
1531                     writeToken();
1532                 }
1533                 else
1534                 {
1535                     write(" ");
1536                     writeToken();
1537                 }
1538                 if (!currentIs(tok!"comment"))
1539                     write(" ");
1540             }
1541             break;
1542         default:
1543             writeToken();
1544             break;
1545         }
1546     }
1547 
1548     void formatComma()
1549     {
1550         import dfmt.editorconfig : OptionalBoolean;
1551         import std.algorithm : canFind;
1552 
1553         if (config.dfmt_keep_line_breaks == OptionalBoolean.f)
1554             regenLineBreakHintsIfNecessary(index);
1555         if (indents.indentToMostRecent(tok!"enum") != -1
1556                 && !peekIs(tok!"}") && indents.topIs(tok!"{") && parenDepth == 0)
1557         {
1558             writeToken();
1559             newline();
1560         }
1561         else if (indents.topIs(tok!"]") && indents.topDetails.breakEveryItem
1562                 && !indents.topDetails.mini)
1563         {
1564             writeToken();
1565             newline();
1566             regenLineBreakHints(index - 1);
1567         }
1568         else if (indents.topIs(tok!"]") && indents.topDetails.preferLongBreaking
1569                 && !currentIs(tok!")") && !currentIs(tok!"]") && !currentIs(tok!"}")
1570                 && !currentIs(tok!"comment") && index + 1 < tokens.length
1571                 && isMultilineAt(index + 1, true))
1572         {
1573             writeToken();
1574             newline();
1575             regenLineBreakHints(index - 1);
1576         }
1577         else if (config.dfmt_keep_line_breaks == OptionalBoolean.t)
1578         {
1579             const commaLine = tokens[index].line;
1580 
1581             writeToken();
1582             if (!currentIs(tok!")") && !currentIs(tok!"]")
1583                     && !currentIs(tok!"}") && !currentIs(tok!"comment"))
1584             {
1585                 if (tokens[index].line == commaLine)
1586                 {
1587                     write(" ");
1588                 }
1589                 else
1590                 {
1591                     newline();
1592                 }
1593             }
1594         }
1595         else if (!peekIs(tok!"}") && (linebreakHints.canFind(index)
1596                 || (linebreakHints.length == 0 && currentLineLength > config.max_line_length)))
1597         {
1598             pushWrapIndent();
1599             writeToken();
1600             newline();
1601         }
1602         else
1603         {
1604             writeToken();
1605             if (!currentIs(tok!")") && !currentIs(tok!"]")
1606                     && !currentIs(tok!"}") && !currentIs(tok!"comment"))
1607             {
1608                 write(" ");
1609             }
1610         }
1611         regenLineBreakHintsIfNecessary(index - 1);
1612     }
1613 
1614     void regenLineBreakHints(immutable size_t i)
1615     {
1616         import std.range : assumeSorted;
1617         import std.algorithm.comparison : min;
1618         import std.algorithm.searching : canFind, countUntil;
1619 
1620         // The end of the tokens considered by the line break algorithm is
1621         // either the expression end index or the next mandatory line break
1622         // or a newline inside a string literal, whichever is first.
1623         auto r = assumeSorted(astInformation.ufcsHintLocations).upperBound(tokens[i].index);
1624         immutable ufcsBreakLocation = r.empty
1625             ? size_t.max
1626             : tokens[i .. $].countUntil!(t => t.index == r.front) + i;
1627         immutable multilineStringLocation = tokens[i .. $]
1628             .countUntil!(t => t.text.canFind('\n'));
1629         immutable size_t j = min(
1630                 expressionEndIndex(i),
1631                 ufcsBreakLocation,
1632                 multilineStringLocation == -1 ? size_t.max : multilineStringLocation + i + 1);
1633         // Use magical negative value for array literals and wrap indents
1634         immutable inLvl = (indents.topIsWrap() || indents.topIs(tok!"]")) ? -indentLevel
1635             : indentLevel;
1636         linebreakHints = chooseLineBreakTokens(i, tokens[i .. j], depths[i .. j],
1637                 config, currentLineLength, inLvl);
1638     }
1639 
1640     void regenLineBreakHintsIfNecessary(immutable size_t i)
1641     {
1642         if (linebreakHints.length == 0 || linebreakHints[$ - 1] <= i - 1)
1643             regenLineBreakHints(i);
1644     }
1645 
1646     void simpleNewline()
1647     {
1648         import dfmt.editorconfig : EOL;
1649 
1650         output.put(eolString);
1651     }
1652 
1653     void newline()
1654     {
1655         import std.range : assumeSorted;
1656         import std.algorithm : max, canFind;
1657         import dfmt.editorconfig : OptionalBoolean;
1658 
1659         if (currentIs(tok!"comment") && index > 0 && current.line == tokenEndLine(tokens[index - 1]))
1660             return;
1661 
1662         immutable bool hasCurrent = this.hasCurrent;
1663 
1664         if (niBraceDepth > 0 && !peekBackIsSlashSlash() && hasCurrent && tokens[index].type == tok!"}"
1665                 && !assumeSorted(astInformation.funLitEndLocations).equalRange(
1666                     tokens[index].index).empty)
1667         {
1668             return;
1669         }
1670 
1671         simpleNewline();
1672 
1673         if (!justAddedExtraNewline && index > 0 && hasCurrent
1674                 && tokens[index].line - tokenEndLine(tokens[index - 1]) > 1)
1675         {
1676             simpleNewline();
1677         }
1678 
1679         justAddedExtraNewline = false;
1680         currentLineLength = 0;
1681 
1682         if (hasCurrent)
1683         {
1684             if (currentIs(tok!"else"))
1685             {
1686                 immutable i = indents.indentToMostRecent(tok!"if");
1687                 immutable v = indents.indentToMostRecent(tok!"version");
1688                 immutable mostRecent = max(i, v);
1689                 if (mostRecent != -1)
1690                     indentLevel = mostRecent;
1691             }
1692             else if (currentIs(tok!"identifier") && peekIs(tok!":"))
1693             {
1694                 if (peekBackIs(tok!"}", true) || peekBackIs(tok!";", true))
1695                     indents.popTempIndents();
1696                 immutable l = indents.indentToMostRecent(tok!"switch");
1697                 if (l != -1 && config.dfmt_align_switch_statements == OptionalBoolean.t)
1698                     indentLevel = l;
1699                 else if (astInformation.structInfoSortedByEndLocation
1700                     .canFind!(st => st.startLocation < current.index && current.index < st.endLocation)) {
1701                     immutable l2 = indents.indentToMostRecent(tok!"{");
1702                     assert(l2 != -1, "Recent '{' is not found despite being in struct initializer");
1703                     indentLevel = l2 + 1;
1704                 }
1705                 else if ((config.dfmt_compact_labeled_statements == OptionalBoolean.f
1706                         || !isBlockHeader(2) || peek2Is(tok!"if")) && !indents.topIs(tok!"]"))
1707                 {
1708                     immutable l2 = indents.indentToMostRecent(tok!"{");
1709                     indentLevel = l2 != -1 ? l2 : indents.indentLevel - 1;
1710                 }
1711                 else
1712                     indentLevel = indents.indentLevel;
1713             }
1714             else if (currentIs(tok!"case") || currentIs(tok!"default"))
1715             {
1716 
1717                 if (peekBackIs(tok!"}", true) || peekBackIs(tok!";", true)
1718                     /**
1719                      * The following code is valid and should be indented flatly
1720                      * case A:
1721                      * case B:
1722                      */
1723                     || peekBackIs(tok!":", true))
1724                 {
1725                     indents.popTempIndents();
1726                     if (indents.topIs(tok!"case"))
1727                         indents.pop();
1728                 }
1729                 immutable l = indents.indentToMostRecent(tok!"switch");
1730                 if (l != -1)
1731                     indentLevel = config.dfmt_align_switch_statements == OptionalBoolean.t
1732                         ? l : indents.indentLevel;
1733             }
1734             else if (currentIs(tok!")"))
1735             {
1736                 if (indents.topIs(tok!"("))
1737                     indents.pop();
1738                 indentLevel = indents.indentLevel;
1739             }
1740             else if (currentIs(tok!"{"))
1741             {
1742                 indents.popWrapIndents();
1743                 if ((peekBackIsSlashSlash() && peekBack2Is(tok!";")) || indents.topIs(tok!"]"))
1744                 {
1745                     indents.popTempIndents();
1746                     indentLevel = indents.indentLevel;
1747                 }
1748             }
1749             else if (currentIs(tok!"}"))
1750             {
1751                 indents.popTempIndents();
1752                 while (indents.topIsOneOf(tok!"case", tok!"@", tok!"static"))
1753                     indents.pop();
1754                 if (indents.topIs(tok!"{"))
1755                 {
1756                     indentLevel = indents.indentToMostRecent(tok!"{");
1757                     indents.pop();
1758                 }
1759                 if (indents.topIsOneOf(tok!"try", tok!"catch"))
1760                 {
1761                     indents.pop();
1762                 }
1763                 else while (sBraceDepth == 0 && indents.topIsTemp()
1764                         && ((!indents.topIsOneOf(tok!"else", tok!"if",
1765                             tok!"static", tok!"version")) || !peekIs(tok!"else")))
1766                 {
1767                     indents.pop();
1768                 }
1769             }
1770             else if (currentIs(tok!"]"))
1771             {
1772                 indents.popWrapIndents();
1773                 if (indents.topIs(tok!"]"))
1774                 {
1775                     indents.pop();
1776                     indentLevel = indents.indentLevel;
1777                 }
1778             }
1779             else if (astInformation.attributeDeclarationLines.canFindIndex(current.line))
1780             {
1781                 if (config.dfmt_outdent_attributes == OptionalBoolean.t)
1782                 {
1783                     immutable l = indents.indentToMostRecent(tok!"{");
1784                     if (l != -1)
1785                         indentLevel = l;
1786                 }
1787                 else
1788                 {
1789                     if (indents.topIs(tok!"@"))
1790                         indents.pop();
1791                     indentLevel = indents.indentLevel;
1792                 }
1793             }
1794             else if (currentIs(tok!"catch") || currentIs(tok!"finally"))
1795             {
1796                 indentLevel = indents.indentLevel;
1797             }
1798             else
1799             {
1800                 if (indents.topIsTemp() && (peekBackIsOneOf(true, tok!"}",
1801                         tok!";") && !indents.topIs(tok!";")))
1802                     indents.popTempIndents();
1803                 indentLevel = indents.indentLevel;
1804             }
1805             indent();
1806         }
1807     }
1808 
1809     void write(string str)
1810     {
1811         currentLineLength += str.length;
1812         output.put(str);
1813     }
1814 
1815     void writeToken()
1816     {
1817         import std.range:retro;
1818         import std.algorithm.searching:countUntil;
1819 
1820         if (current.text is null)
1821         {
1822             immutable s = str(current.type);
1823             currentLineLength += s.length;
1824             output.put(str(current.type));
1825         }
1826         else
1827         {
1828             // You know what's awesome? Windows can't handle its own line
1829             // endings correctly.
1830             version (Windows)
1831                 output.put(current.text.replace("\r", ""));
1832             else
1833                 output.put(current.text);
1834             switch (current.type)
1835             {
1836             case tok!"stringLiteral":
1837             case tok!"wstringLiteral":
1838             case tok!"dstringLiteral":
1839                 immutable o = current.text.retro().countUntil('\n');
1840                 if (o == -1) {
1841                     currentLineLength += current.text.length;
1842                 } else {
1843                     currentLineLength = cast(uint) o;
1844                 }
1845                 break;
1846             default:
1847                 currentLineLength += current.text.length;
1848                 break;
1849             }
1850         }
1851         index++;
1852     }
1853 
1854     void writeParens(bool spaceAfter)
1855     in
1856     {
1857         assert(currentIs(tok!"("), str(current.type));
1858     }
1859     do
1860     {
1861         immutable int depth = parenDepth;
1862         immutable int startingNiBraceDepth = niBraceDepth;
1863         immutable int startingSBraceDepth = sBraceDepth;
1864         parenDepth = 0;
1865 
1866         do
1867         {
1868             spaceAfterParens = spaceAfter;
1869             if (currentIs(tok!";") && niBraceDepth <= startingNiBraceDepth
1870                     && sBraceDepth <= startingSBraceDepth)
1871             {
1872                 if (currentLineLength >= config.dfmt_soft_max_line_length)
1873                 {
1874                     pushWrapIndent();
1875                     writeToken();
1876                     newline();
1877                 }
1878                 else
1879                 {
1880                     writeToken();
1881                     if (!currentIs(tok!")") && !currentIs(tok!";"))
1882                         write(" ");
1883                 }
1884             }
1885             else
1886                 formatStep();
1887         }
1888         while (hasCurrent && parenDepth > 0);
1889 
1890         if (indents.topIs(tok!"!"))
1891             indents.pop();
1892         parenDepth = depth;
1893         spaceAfterParens = spaceAfter;
1894     }
1895 
1896     void indent()
1897     {
1898         import dfmt.editorconfig : IndentStyle;
1899 
1900         if (config.indent_style == IndentStyle.tab)
1901         {
1902             foreach (i; 0 .. indentLevel)
1903             {
1904                 currentLineLength += config.tab_width;
1905                 output.put("\t");
1906             }
1907         }
1908         else
1909         {
1910             foreach (i; 0 .. indentLevel)
1911                 foreach (j; 0 .. config.indent_size)
1912                 {
1913                     output.put(" ");
1914                     currentLineLength++;
1915                 }
1916         }
1917     }
1918 
1919     void pushWrapIndent(IdType type = tok!"")
1920     {
1921         immutable t = type == tok!"" ? tokens[index].type : type;
1922         IndentStack.Details detail;
1923         detail.wrap = isWrapIndent(t);
1924         detail.temp = isTempIndent(t);
1925         pushWrapIndent(t, detail);
1926     }
1927 
1928     void pushWrapIndent(IdType type, IndentStack.Details detail)
1929     {
1930         if (parenDepth == 0)
1931         {
1932             if (indents.wrapIndents == 0)
1933                 indents.push(type, detail);
1934         }
1935         else if (indents.wrapIndents < 1)
1936             indents.push(type, detail);
1937     }
1938 
1939     void writeSpace()
1940     {
1941         if (onNextLine)
1942         {
1943             newline();
1944         }
1945         else
1946         {
1947             write(" ");
1948         }
1949     }
1950 
1951 const pure @safe @nogc:
1952 
1953     size_t expressionEndIndex(size_t i, bool matchComma = false) nothrow
1954     {
1955         immutable bool braces = i < tokens.length && tokens[i].type == tok!"{";
1956         immutable bool brackets = i < tokens.length && tokens[i].type == tok!"[";
1957         immutable d = depths[i];
1958         while (true)
1959         {
1960             if (i >= tokens.length)
1961                 break;
1962             if (depths[i] < d)
1963                 break;
1964             if (!braces && !brackets && matchComma && depths[i] == d && tokens[i].type == tok!",")
1965                 break;
1966             if (!braces && !brackets && (tokens[i].type == tok!";" || tokens[i].type == tok!"{"))
1967                 break;
1968             i++;
1969         }
1970         return i;
1971     }
1972 
1973     /// Returns: true when the expression starting at index goes over the line length limit.
1974     /// Uses matching `{}` or `[]` or otherwise takes everything up until a semicolon or opening brace using expressionEndIndex.
1975     bool isMultilineAt(size_t i, bool matchComma = false)
1976     {
1977         import std.algorithm : map, sum, canFind;
1978 
1979         auto e = expressionEndIndex(i, matchComma);
1980         immutable int l = currentLineLength + tokens[i .. e].map!(a => tokenLength(a)).sum();
1981         return l > config.dfmt_soft_max_line_length || tokens[i .. e].canFind!(
1982                 a => a.type == tok!"comment" || isBlockHeaderToken(a.type))();
1983     }
1984 
1985     bool peekIsKeyword() nothrow
1986     {
1987         return index + 1 < tokens.length && isKeyword(tokens[index + 1].type);
1988     }
1989 
1990     bool peekIsBasicType() nothrow
1991     {
1992         return index + 1 < tokens.length && isBasicType(tokens[index + 1].type);
1993     }
1994 
1995     bool peekIsLabel() nothrow
1996     {
1997         return peekIs(tok!"identifier") && peek2Is(tok!":");
1998     }
1999 
2000     int currentTokenLength()
2001     {
2002         return tokenLength(tokens[index]);
2003     }
2004 
2005     int nextTokenLength()
2006     {
2007         immutable size_t i = index + 1;
2008         if (i >= tokens.length)
2009             return INVALID_TOKEN_LENGTH;
2010         return tokenLength(tokens[i]);
2011     }
2012 
2013     bool hasCurrent() nothrow const
2014     {
2015         return index < tokens.length;
2016     }
2017 
2018     ref current() nothrow
2019     in
2020     {
2021         assert(hasCurrent);
2022     }
2023     do
2024     {
2025         return tokens[index];
2026     }
2027 
2028     const(Token) peekBack(uint distance = 1) nothrow
2029     {
2030         assert(index >= distance, "Trying to peek before the first token");
2031         return tokens[index - distance];
2032     }
2033 
2034     bool peekBackIsLiteralOrIdent() nothrow
2035     {
2036         if (index == 0)
2037             return false;
2038         switch (tokens[index - 1].type)
2039         {
2040         case tok!"doubleLiteral":
2041         case tok!"floatLiteral":
2042         case tok!"idoubleLiteral":
2043         case tok!"ifloatLiteral":
2044         case tok!"intLiteral":
2045         case tok!"longLiteral":
2046         case tok!"realLiteral":
2047         case tok!"irealLiteral":
2048         case tok!"uintLiteral":
2049         case tok!"ulongLiteral":
2050         case tok!"characterLiteral":
2051         case tok!"identifier":
2052         case tok!"stringLiteral":
2053         case tok!"wstringLiteral":
2054         case tok!"dstringLiteral":
2055         case tok!"true":
2056         case tok!"false":
2057             return true;
2058         default:
2059             return false;
2060         }
2061     }
2062 
2063     bool peekIsLiteralOrIdent() nothrow
2064     {
2065         if (index + 1 >= tokens.length)
2066             return false;
2067         switch (tokens[index + 1].type)
2068         {
2069         case tok!"doubleLiteral":
2070         case tok!"floatLiteral":
2071         case tok!"idoubleLiteral":
2072         case tok!"ifloatLiteral":
2073         case tok!"intLiteral":
2074         case tok!"longLiteral":
2075         case tok!"realLiteral":
2076         case tok!"irealLiteral":
2077         case tok!"uintLiteral":
2078         case tok!"ulongLiteral":
2079         case tok!"characterLiteral":
2080         case tok!"identifier":
2081         case tok!"stringLiteral":
2082         case tok!"wstringLiteral":
2083         case tok!"dstringLiteral":
2084             return true;
2085         default:
2086             return false;
2087         }
2088     }
2089 
2090     bool peekBackIs(IdType tokenType, bool ignoreComments = false) nothrow
2091     {
2092         return peekImplementation(tokenType, -1, ignoreComments);
2093     }
2094 
2095     bool peekBackIsKeyword(bool ignoreComments = true) nothrow
2096     {
2097         if (index == 0)
2098             return false;
2099         auto i = index - 1;
2100         if (ignoreComments)
2101             while (tokens[i].type == tok!"comment")
2102             {
2103                 if (i == 0)
2104                     return false;
2105                 i--;
2106             }
2107         return isKeyword(tokens[i].type);
2108     }
2109 
2110     bool peekBackIsOperator() nothrow
2111     {
2112         return index == 0 ? false : isOperator(tokens[index - 1].type);
2113     }
2114 
2115     bool peekBackIsOneOf(bool ignoreComments, IdType[] tokenTypes...) nothrow
2116     {
2117         if (index == 0)
2118             return false;
2119         auto i = index - 1;
2120         if (ignoreComments)
2121             while (tokens[i].type == tok!"comment")
2122             {
2123                 if (i == 0)
2124                     return false;
2125                 i--;
2126             }
2127         immutable t = tokens[i].type;
2128         foreach (tt; tokenTypes)
2129             if (tt == t)
2130                 return true;
2131         return false;
2132     }
2133 
2134     bool peekBack2Is(IdType tokenType, bool ignoreComments = false) nothrow
2135     {
2136         return peekImplementation(tokenType, -2, ignoreComments);
2137     }
2138 
2139     bool peekImplementation(IdType tokenType, int n, bool ignoreComments = true) nothrow
2140     {
2141         auto i = index + n;
2142         if (ignoreComments)
2143             while (n != 0 && i < tokens.length && tokens[i].type == tok!"comment")
2144                 i = n > 0 ? i + 1 : i - 1;
2145         return i < tokens.length && tokens[i].type == tokenType;
2146     }
2147 
2148     bool peek2Is(IdType tokenType, bool ignoreComments = true) nothrow
2149     {
2150         return peekImplementation(tokenType, 2, ignoreComments);
2151     }
2152 
2153     bool peekIsOperator() nothrow
2154     {
2155         return index + 1 < tokens.length && isOperator(tokens[index + 1].type);
2156     }
2157 
2158     bool peekIs(IdType tokenType, bool ignoreComments = true) nothrow
2159     {
2160         return peekImplementation(tokenType, 1, ignoreComments);
2161     }
2162 
2163     bool peekIsBody() nothrow
2164     {
2165         return index + 1 < tokens.length && tokens[index + 1].text == "body";
2166     }
2167 
2168     bool peekBackIsFunctionDeclarationEnding() nothrow
2169     {
2170         return peekBackIsOneOf(false, tok!")", tok!"const", tok!"immutable",
2171             tok!"inout", tok!"shared", tok!"@", tok!"pure", tok!"nothrow",
2172             tok!"return", tok!"scope");
2173     }
2174 
2175     bool peekBackIsSlashSlash() nothrow
2176     {
2177         return index > 0 && tokens[index - 1].type == tok!"comment"
2178             && tokens[index - 1].text[0 .. 2] == "//";
2179     }
2180 
2181     bool currentIs(IdType tokenType) nothrow
2182     {
2183         return hasCurrent && tokens[index].type == tokenType;
2184     }
2185 
2186     bool onNextLine() @nogc nothrow pure @safe
2187     {
2188         import dfmt.editorconfig : OptionalBoolean;
2189         import std.algorithm.searching : count;
2190         import std..string : representation;
2191 
2192         if (config.dfmt_keep_line_breaks == OptionalBoolean.f || index <= 0)
2193         {
2194             return false;
2195         }
2196         // To compare whether 2 tokens are on same line, we need the end line
2197         // of the first token (tokens[index - 1]) and the start line of the
2198         // second one (tokens[index]). If a token takes multiple lines (e.g. a
2199         // multi-line string), we can sum the number of the newlines in the
2200         // token and tokens[index - 1].line, the start line.
2201         const previousTokenEndLineNo = tokens[index - 1].line
2202                 + tokens[index - 1].text.representation.count('\n');
2203 
2204         return previousTokenEndLineNo < tokens[index].line;
2205     }
2206 
2207     /// Bugs: not unicode correct
2208     size_t tokenEndLine(const Token t)
2209     {
2210         import std.algorithm : count;
2211 
2212         switch (t.type)
2213         {
2214         case tok!"comment":
2215         case tok!"stringLiteral":
2216         case tok!"wstringLiteral":
2217         case tok!"dstringLiteral":
2218             return t.line + t.text.count('\n');
2219         default:
2220             return t.line;
2221         }
2222     }
2223 
2224     bool isBlockHeaderToken(const IdType t)
2225     {
2226         return t == tok!"for" || t == tok!"foreach" || t == tok!"foreach_reverse"
2227             || t == tok!"while" || t == tok!"if" || t == tok!"in"|| t == tok!"out"
2228             || t == tok!"do" || t == tok!"catch" || t == tok!"with"
2229             || t == tok!"synchronized" || t == tok!"scope" || t == tok!"debug";
2230     }
2231 
2232     bool isBlockHeader(int i = 0) nothrow
2233     {
2234         if (i + index < 0 || i + index >= tokens.length)
2235             return false;
2236         const t = tokens[i + index].type;
2237         bool isExpressionContract;
2238 
2239         if (i + index + 3 < tokens.length)
2240         {
2241             isExpressionContract = (t == tok!"in" && peekImplementation(tok!"(", i + 1, true))
2242                 || (t == tok!"out" && (peekImplementation(tok!"(", i + 1, true)
2243                     && (peekImplementation(tok!";", i + 2, true)
2244                         || (peekImplementation(tok!"identifier", i + 2, true)
2245                             && peekImplementation(tok!";", i + 3, true)))));
2246         }
2247 
2248         return isBlockHeaderToken(t) && !isExpressionContract;
2249     }
2250 
2251     bool isSeparationToken(IdType t) nothrow
2252     {
2253         return t == tok!"," || t == tok!";" || t == tok!":" || t == tok!"("
2254             || t == tok!")" || t == tok!"[" || t == tok!"]" || t == tok!"{" || t == tok!"}";
2255     }
2256 }
2257 
2258 bool canFindIndex(const size_t[] items, size_t index, size_t* pos = null) pure @safe @nogc
2259 {
2260     import std.range : assumeSorted;
2261     if (!pos)
2262     {
2263         return !assumeSorted(items).equalRange(index).empty;
2264     }
2265     else
2266     {
2267         auto trisection_result = assumeSorted(items).trisect(index);
2268         if (trisection_result[1].length == 1)
2269         {
2270             *pos = trisection_result[0].length;
2271             return true;
2272         }
2273         else if (trisection_result[1].length == 0)
2274         {
2275             return false;
2276         }
2277         else
2278         {
2279             assert(0, "the constraint of having unique locations has been violated");
2280         }
2281     }
2282 }