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