@@ -21,10 +21,6 @@ import 'solution_cache.dart';
21
21
class CodeWriter {
22
22
final int _pageWidth;
23
23
24
- /// The number of spaces of leading indentation at the beginning of each line
25
- /// independent of indentation created by pieces being written.
26
- final int _leadingIndent;
27
-
28
24
/// Previously cached formatted subtrees.
29
25
final SolutionCache _cache;
30
26
@@ -50,17 +46,23 @@ class CodeWriter {
50
46
/// The number of characters in the line currently being written.
51
47
int _column = 0 ;
52
48
53
- /// The stack of state for each [Piece] being formatted.
54
- ///
55
- /// For each piece being formatted from a call to [format()] , we keep track of
56
- /// things like indentation and nesting levels. Pieces recursively format
57
- /// their children. When they do, we push new values onto this stack. When a
58
- /// piece is done (a call to [format()] returns), we pop the corresponding
59
- /// state off the stack.
49
+ /// The stack indentation levels.
60
50
///
61
- /// This is used to increase the cumulative nesting as we recurse into pieces
62
- /// and then unwind that as child pieces are completed.
63
- final List <_PieceOptions > _options = [];
51
+ /// Each entry in the stack is the absolute number of spaces of leading
52
+ /// indentation that should be written when beginning a new line to account
53
+ /// for block nesting, expression wrapping, constructor initializers, etc.
54
+ final List <_Indent > _indentStack = [];
55
+
56
+ /// The stack of regions created by pairs of calls to [pushAllowNewlines()]
57
+ /// and [popAllowNewlines()] .
58
+ final List <bool > _allowNewlineStack = [true ];
59
+
60
+ /// Whether any newlines have been written during the [_currentPiece] being
61
+ /// formatted.
62
+ bool _hadNewline = false ;
63
+
64
+ /// The current innermost piece being formatted by a call to [format()] .
65
+ Piece ? _currentPiece;
64
66
65
67
/// Whether we have already found the first line where whose piece should be
66
68
/// used to expand further solutions.
@@ -94,11 +96,15 @@ class CodeWriter {
94
96
/// solution if the line ends up overflowing.
95
97
final List <Piece > _currentUnsolvedPieces = [];
96
98
97
- CodeWriter (
98
- this ._pageWidth, this ._leadingIndent, this ._cache, this ._solution) {
99
+ /// [leadingIndent] is the number of spaces of leading indentation at the
100
+ /// beginning of each line independent of indentation created by pieces being
101
+ /// written.
102
+ CodeWriter (this ._pageWidth, int leadingIndent, this ._cache, this ._solution) {
103
+ _indentStack.add (_Indent (leadingIndent, 0 ));
104
+
99
105
// Write the leading indent before the first line.
100
- _buffer.write (' ' * _leadingIndent );
101
- _column = _leadingIndent ;
106
+ _buffer.write (' ' * leadingIndent );
107
+ _column = leadingIndent ;
102
108
}
103
109
104
110
/// Returns the final formatted text and the next piece that can be expanded
@@ -135,35 +141,60 @@ class CodeWriter {
135
141
}
136
142
}
137
143
138
- /// Sets the number of spaces of indentation for code written by the current
139
- /// piece to [indent] , relative to the indentation of the surrounding piece .
144
+ /// Increases the number of spaces of indentation by [indent] relative to the
145
+ /// current amount of indentation .
140
146
///
141
- /// Replaces any previous indentation set by this piece.
142
- // TODO(tall): Add another API that adds/subtracts existing indentation.
143
- void setIndent (int indent) {
144
- var parentIndent = _leadingIndent;
145
-
146
- // If there is a surrounding Piece, then set the indent relative to that
147
- // piece's current indentation.
148
- if (_options.length > 1 ) {
149
- parentIndent = _options[_options.length - 2 ].indent;
147
+ /// If [canCollapse] is `true` , then the new [indent] spaces of indentation
148
+ /// are "collapsible". This means that further calls to [pushIndent()] will
149
+ /// merge their indentation with [indent] and not increase the visible
150
+ /// indentation until more than [indent] spaces of indentation have been
151
+ /// increased.
152
+ void pushIndent (int indent, {bool canCollapse = false }) {
153
+ var parentIndent = _indentStack.last.indent;
154
+ var parentCollapse = _indentStack.last.collapsible;
155
+
156
+ if (canCollapse) {
157
+ // Increase the indent and the collapsible indent.
158
+ _indentStack.add (_Indent (parentIndent + indent, parentCollapse + indent));
159
+ } else if (parentCollapse > indent) {
160
+ // All new indent is collapsed with the existing collapsible indent.
161
+ _indentStack.add (_Indent (parentIndent, parentCollapse - indent));
162
+ } else {
163
+ // Use up the collapsible indent (if any) and then indent by the rest.
164
+ indent -= parentCollapse;
165
+ _indentStack.add (_Indent (parentIndent + indent, 0 ));
150
166
}
167
+ }
168
+
169
+ /// Discards the indentation change from the last call to [pushIndent()] .
170
+ void popIndent () {
171
+ _indentStack.removeLast ();
172
+ }
173
+
174
+ /// Begins a region of formatting where newlines are allowed if [allow] is
175
+ /// `true` or prohibited otherwise.
176
+ ///
177
+ /// If a newline is written while the top of the stack is `false` , the entire
178
+ /// solution is considered invalid and gets discarded.
179
+ ///
180
+ /// The region is ended by a corresponding call to [popAllowNewlines()] .
181
+ void pushAllowNewlines (bool allow) {
182
+ _allowNewlineStack.add (allow);
183
+ }
151
184
152
- _options.last.indent = parentIndent + indent;
185
+ /// Ends the region begun by the most recent call to [pushAllowNewlines()] .
186
+ void popAllowNewlines () {
187
+ _allowNewlineStack.removeLast ();
153
188
}
154
189
155
190
/// Inserts a newline if [condition] is true.
156
191
///
157
192
/// If [space] is `true` and [condition] is `false` , writes a space.
158
193
///
159
194
/// If [blank] is `true` , writes an extra newline to produce a blank line.
160
- ///
161
- /// If [indent] is given, sets the amount of block-level indentation for this
162
- /// and all subsequent newlines to [indent] .
163
- void splitIf (bool condition,
164
- {bool space = true , bool blank = false , int ? indent}) {
195
+ void splitIf (bool condition, {bool space = true , bool blank = false }) {
165
196
if (condition) {
166
- newline (blank: blank, indent : indent );
197
+ newline (blank: blank);
167
198
} else if (space) {
168
199
this .space ();
169
200
}
@@ -178,15 +209,10 @@ class CodeWriter {
178
209
///
179
210
/// If [blank] is `true` , writes an extra newline to produce a blank line.
180
211
///
181
- /// If [indent] is given, set the indentation of the new line (and all
182
- /// subsequent lines) to that indentation relative to the containing piece.
183
- ///
184
212
/// If [flushLeft] is `true` , then the new line begins at column 1 and ignores
185
213
/// any surrounding indentation. This is used for multi-line block comments
186
214
/// and multi-line strings.
187
- void newline ({bool blank = false , int ? indent, bool flushLeft = false }) {
188
- if (indent != null ) setIndent (indent);
189
-
215
+ void newline ({bool blank = false , bool flushLeft = false }) {
190
216
whitespace (blank ? Whitespace .blankLine : Whitespace .newline,
191
217
flushLeft: flushLeft);
192
218
}
@@ -203,18 +229,12 @@ class CodeWriter {
203
229
void whitespace (Whitespace whitespace, {bool flushLeft = false }) {
204
230
if (whitespace case Whitespace .newline || Whitespace .blankLine) {
205
231
_handleNewline ();
206
- _pendingIndent = flushLeft ? 0 : _options .last.indent;
232
+ _pendingIndent = flushLeft ? 0 : _indentStack .last.indent;
207
233
}
208
234
209
235
_pendingWhitespace = _pendingWhitespace.collapse (whitespace);
210
236
}
211
237
212
- /// Sets whether newlines are allowed to occur from this point on for the
213
- /// current piece.
214
- void setAllowNewlines (bool allowed) {
215
- _options.last.allowNewlines = allowed;
216
- }
217
-
218
238
/// Format [piece] and insert the result into the code being written and
219
239
/// returned by [finish()] .
220
240
///
@@ -264,24 +284,30 @@ class CodeWriter {
264
284
265
285
/// Format [piece] writing directly into this [CodeWriter] .
266
286
void _formatInline (Piece piece) {
267
- _options.add (_PieceOptions (
268
- piece,
269
- _options.lastOrNull? .indent ?? _leadingIndent,
270
- _options.lastOrNull? .allowNewlines ?? true ));
287
+ // Begin a new formatting context for this child.
288
+ var previousPiece = _currentPiece;
289
+ _currentPiece = piece;
290
+
291
+ var previousHadNewline = _hadNewline;
292
+ _hadNewline = false ;
271
293
272
294
var isUnsolved =
273
295
! _solution.isBound (piece) && piece.additionalStates.isNotEmpty;
274
296
if (isUnsolved) _currentUnsolvedPieces.add (piece);
275
297
298
+ // Format the child piece.
276
299
piece.format (this , _solution.pieceState (piece));
277
300
301
+ // Restore the surrounding piece's context.
278
302
if (isUnsolved) _currentUnsolvedPieces.removeLast ();
279
303
280
- var childOptions = _options.removeLast ();
304
+ var childHadNewline = _hadNewline;
305
+ _hadNewline = previousHadNewline;
306
+
307
+ _currentPiece = previousPiece;
281
308
282
- // If the child [piece] contains a newline then this one transitively
283
- // does.
284
- if (childOptions.hasNewline && _options.isNotEmpty) _handleNewline ();
309
+ // If the child contained a newline then the parent transitively does.
310
+ if (childHadNewline && _currentPiece != null ) _handleNewline ();
285
311
}
286
312
287
313
/// Sets [selectionStart] to be [start] code units into the output.
@@ -301,11 +327,11 @@ class CodeWriter {
301
327
/// If this occurs in a place where newlines are prohibited, then invalidates
302
328
/// the solution.
303
329
void _handleNewline () {
304
- if (! _options .last.allowNewlines ) _solution.invalidate (_options.last.piece );
330
+ if (! _allowNewlineStack .last) _solution.invalidate (_currentPiece ! );
305
331
306
332
// Note that this piece contains a newline so that we can propagate that
307
333
// up to containing pieces too.
308
- _options.last.hasNewline = true ;
334
+ _hadNewline = true ;
309
335
}
310
336
311
337
/// Write any pending whitespace.
@@ -387,24 +413,13 @@ enum Whitespace {
387
413
};
388
414
}
389
415
390
- /// The mutable state local to a single piece being formatted.
391
- class _PieceOptions {
392
- /// The piece being formatted with these options.
393
- final Piece piece;
394
-
395
- /// The absolute number of spaces of leading indentation coming from
396
- /// block-like structure or explicit extra indentation (aligning constructor
397
- /// initializers, `show` clauses, etc.).
398
- int indent;
399
-
400
- /// Whether newlines are allowed to occur.
401
- ///
402
- /// If a newline is written while this is `false` , the entire solution is
403
- /// considered invalid and gets discarded.
404
- bool allowNewlines;
416
+ /// A level of indentation in the indentation stack.
417
+ class _Indent {
418
+ /// The total number of spaces of indentation.
419
+ final int indent;
405
420
406
- /// Whether any newlines have occurred in this piece or any of its children .
407
- bool hasNewline = false ;
421
+ /// How many spaces of [indent] can be collapsed with further indentation .
422
+ final int collapsible ;
408
423
409
- _PieceOptions (this .piece, this . indent, this .allowNewlines );
424
+ _Indent (this .indent, this .collapsible );
410
425
}
0 commit comments