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