OLD | NEW |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library dart_style.src.chunk_builder; | 5 library dart_style.src.chunk_builder; |
6 | 6 |
7 import 'chunk.dart'; | 7 import 'chunk.dart'; |
8 import 'dart_formatter.dart'; | 8 import 'dart_formatter.dart'; |
9 import 'debug.dart' as debug; | 9 import 'debug.dart' as debug; |
10 import 'line_writer.dart'; | 10 import 'line_writer.dart'; |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
54 /// rules may not have all of their constraints fully wired up until after | 54 /// rules may not have all of their constraints fully wired up until after |
55 /// the hard split appears. For example, a hard split in a positional | 55 /// the hard split appears. For example, a hard split in a positional |
56 /// argument list needs to force the named arguments to split too, but we | 56 /// argument list needs to force the named arguments to split too, but we |
57 /// don't create that rule until after the positional arguments are done. | 57 /// don't create that rule until after the positional arguments are done. |
58 final _hardSplitRules = new Set<Rule>(); | 58 final _hardSplitRules = new Set<Rule>(); |
59 | 59 |
60 /// The list of rules that are waiting until the next whitespace has been | 60 /// The list of rules that are waiting until the next whitespace has been |
61 /// written before they start. | 61 /// written before they start. |
62 final _lazyRules = <Rule>[]; | 62 final _lazyRules = <Rule>[]; |
63 | 63 |
64 /// The indexes of the chunks owned by each rule (except for hard splits). | |
65 final _ruleChunks = <Rule, List<int>>{}; | |
66 | |
67 /// The nested stack of spans that are currently being written. | 64 /// The nested stack of spans that are currently being written. |
68 final _openSpans = <OpenSpan>[]; | 65 final _openSpans = <OpenSpan>[]; |
69 | 66 |
70 /// The current state. | 67 /// The current state. |
71 final _nesting = new NestingBuilder(); | 68 final _nesting = new NestingBuilder(); |
72 | 69 |
73 /// The stack of nesting levels where block arguments may start. | 70 /// The stack of nesting levels where block arguments may start. |
74 /// | 71 /// |
75 /// A block argument's contents will nest at the last level in this stack. | 72 /// A block argument's contents will nest at the last level in this stack. |
76 final _blockArgumentNesting = <NestingLevel>[]; | 73 final _blockArgumentNesting = <NestingLevel>[]; |
(...skipping 17 matching lines...) Expand all Loading... |
94 bool _firstFlushLeft = false; | 91 bool _firstFlushLeft = false; |
95 | 92 |
96 /// Whether there is pending whitespace that depends on the number of | 93 /// Whether there is pending whitespace that depends on the number of |
97 /// newlines in the source. | 94 /// newlines in the source. |
98 /// | 95 /// |
99 /// This is used to avoid calculating the newlines between tokens unless | 96 /// This is used to avoid calculating the newlines between tokens unless |
100 /// actually needed since doing so is slow when done between every single | 97 /// actually needed since doing so is slow when done between every single |
101 /// token pair. | 98 /// token pair. |
102 bool get needsToPreserveNewlines => | 99 bool get needsToPreserveNewlines => |
103 _pendingWhitespace == Whitespace.oneOrTwoNewlines || | 100 _pendingWhitespace == Whitespace.oneOrTwoNewlines || |
104 _pendingWhitespace == Whitespace.spaceOrNewline || | 101 _pendingWhitespace == Whitespace.spaceOrNewline || |
105 _pendingWhitespace == Whitespace.splitOrNewline; | 102 _pendingWhitespace == Whitespace.splitOrNewline; |
106 | 103 |
107 /// The number of characters of code that can fit in a single line. | 104 /// The number of characters of code that can fit in a single line. |
108 int get pageWidth => _formatter.pageWidth; | 105 int get pageWidth => _formatter.pageWidth; |
109 | 106 |
110 /// The current innermost rule. | 107 /// The current innermost rule. |
111 Rule get rule => _rules.last; | 108 Rule get rule => _rules.last; |
112 | 109 |
113 ChunkBuilder(this._formatter, this._source) | 110 ChunkBuilder(this._formatter, this._source) |
114 : _parent = null, | 111 : _parent = null, |
115 _chunks = [] { | 112 _chunks = [] { |
(...skipping 28 matching lines...) Expand all Loading... |
144 _nesting.commitNesting(); | 141 _nesting.commitNesting(); |
145 } | 142 } |
146 | 143 |
147 /// Writes a [WhitespaceChunk] of [type]. | 144 /// Writes a [WhitespaceChunk] of [type]. |
148 void writeWhitespace(Whitespace type) { | 145 void writeWhitespace(Whitespace type) { |
149 _pendingWhitespace = type; | 146 _pendingWhitespace = type; |
150 } | 147 } |
151 | 148 |
152 /// Write a split owned by the current innermost rule. | 149 /// Write a split owned by the current innermost rule. |
153 /// | 150 /// |
154 /// If [nesting] is given, uses that. Otherwise, uses the current nesting | |
155 /// level. If unsplit, it expands to a space if [space] is `true`. | |
156 /// | |
157 /// If [flushLeft] is `true`, then forces the next line to start at column | 151 /// If [flushLeft] is `true`, then forces the next line to start at column |
158 /// one regardless of any indentation or nesting. | 152 /// one regardless of any indentation or nesting. |
159 /// | 153 /// |
160 /// If [isDouble] is passed, forces the split to either be a single or double | 154 /// If [isDouble] is passed, forces the split to either be a single or double |
161 /// newline. Otherwise, leaves it indeterminate. | 155 /// newline. Otherwise, leaves it indeterminate. |
162 Chunk split({bool space, bool isDouble, bool flushLeft}) => | |
163 _writeSplit(_rules.last, null, | |
164 flushLeft: flushLeft, isDouble: isDouble, spaceWhenUnsplit: space); | |
165 | |
166 /// Write a split owned by the current innermost rule. | |
167 /// | 156 /// |
168 /// Unlike [split()], this ignores any current expression nesting. It always | 157 /// If [nest] is `false`, ignores any current expression nesting. Otherwise, |
169 /// indents the next line at the statement level. | 158 /// uses the current nesting level. If unsplit, it expands to a space if |
170 Chunk blockSplit({bool space, bool isDouble}) => | 159 /// [space] is `true`. |
171 _writeSplit(_rules.last, _nesting.blockNesting, | 160 Chunk split({bool flushLeft, bool isDouble, bool nest, bool space}) => |
172 isDouble: isDouble, spaceWhenUnsplit: space); | 161 _writeSplit(_rules.last, |
| 162 flushLeft: flushLeft, isDouble: isDouble, nest: nest, space: space); |
173 | 163 |
174 /// Outputs the series of [comments] and associated whitespace that appear | 164 /// Outputs the series of [comments] and associated whitespace that appear |
175 /// before [token] (which is not written by this). | 165 /// before [token] (which is not written by this). |
176 /// | 166 /// |
177 /// The list contains each comment as it appeared in the source between the | 167 /// The list contains each comment as it appeared in the source between the |
178 /// last token written and the next one that's about to be written. | 168 /// last token written and the next one that's about to be written. |
179 /// | 169 /// |
180 /// [linesBeforeToken] is the number of lines between the last comment (or | 170 /// [linesBeforeToken] is the number of lines between the last comment (or |
181 /// previous token if there are no comments) and the next token. | 171 /// previous token if there are no comments) and the next token. |
182 void writeComments( | 172 void writeComments( |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
250 } | 240 } |
251 | 241 |
252 // The comment follows other text, so we need to decide if it gets a | 242 // The comment follows other text, so we need to decide if it gets a |
253 // space before it or not. | 243 // space before it or not. |
254 if (_needsSpaceBeforeComment(isLineComment: comment.isLineComment)) { | 244 if (_needsSpaceBeforeComment(isLineComment: comment.isLineComment)) { |
255 _writeText(" "); | 245 _writeText(" "); |
256 } | 246 } |
257 } else { | 247 } else { |
258 // The comment starts a line, so make sure it stays on its own line. | 248 // The comment starts a line, so make sure it stays on its own line. |
259 _writeHardSplit( | 249 _writeHardSplit( |
260 nest: true, | |
261 flushLeft: comment.flushLeft, | 250 flushLeft: comment.flushLeft, |
262 double: comment.linesBefore > 1); | 251 isDouble: comment.linesBefore > 1, |
| 252 nest: true); |
263 } | 253 } |
264 | 254 |
265 _writeText(comment.text); | 255 _writeText(comment.text); |
266 | 256 |
267 if (comment.selectionStart != null) { | 257 if (comment.selectionStart != null) { |
268 startSelectionFromEnd(comment.text.length - comment.selectionStart); | 258 startSelectionFromEnd(comment.text.length - comment.selectionStart); |
269 } | 259 } |
270 | 260 |
271 if (comment.selectionEnd != null) { | 261 if (comment.selectionEnd != null) { |
272 endSelectionFromEnd(comment.text.length - comment.selectionEnd); | 262 endSelectionFromEnd(comment.text.length - comment.selectionEnd); |
(...skipping 11 matching lines...) Expand all Loading... |
284 // mistakes like: | 274 // mistakes like: |
285 // | 275 // |
286 // /** | 276 // /** |
287 // * Some doc comment. | 277 // * Some doc comment. |
288 // */ someFunction() { ... } | 278 // */ someFunction() { ... } |
289 if (linesAfter == 0 && comments.last.text.contains("\n")) { | 279 if (linesAfter == 0 && comments.last.text.contains("\n")) { |
290 linesAfter = 1; | 280 linesAfter = 1; |
291 } | 281 } |
292 } | 282 } |
293 | 283 |
294 if (linesAfter > 0) _writeHardSplit(nest: true, double: linesAfter > 1); | 284 if (linesAfter > 0) _writeHardSplit(isDouble: linesAfter > 1, nest: true); |
295 } | 285 } |
296 | 286 |
297 // If the comment has text following it (aside from a grouping character), | 287 // If the comment has text following it (aside from a grouping character), |
298 // it needs a trailing space. | 288 // it needs a trailing space. |
299 if (_needsSpaceAfterLastComment(comments, token)) { | 289 if (_needsSpaceAfterLastComment(comments, token)) { |
300 _pendingWhitespace = Whitespace.space; | 290 _pendingWhitespace = Whitespace.space; |
301 } | 291 } |
302 | 292 |
303 preserveNewlines(linesBeforeToken); | 293 preserveNewlines(linesBeforeToken); |
304 } | 294 } |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
361 var openSpan = _openSpans.removeLast(); | 351 var openSpan = _openSpans.removeLast(); |
362 | 352 |
363 // A span that just covers a single chunk can't be split anyway. | 353 // A span that just covers a single chunk can't be split anyway. |
364 var end = _currentChunkIndex; | 354 var end = _currentChunkIndex; |
365 if (openSpan.start == end) return; | 355 if (openSpan.start == end) return; |
366 | 356 |
367 // Add the span to every chunk that can split it. | 357 // Add the span to every chunk that can split it. |
368 var span = new Span(openSpan.cost); | 358 var span = new Span(openSpan.cost); |
369 for (var i = openSpan.start; i < end; i++) { | 359 for (var i = openSpan.start; i < end; i++) { |
370 var chunk = _chunks[i]; | 360 var chunk = _chunks[i]; |
371 if (!chunk.isHardSplit) chunk.spans.add(span); | 361 if (!chunk.rule.isHardened) chunk.spans.add(span); |
372 } | 362 } |
373 } | 363 } |
374 | 364 |
375 /// Starts a new [Rule]. | 365 /// Starts a new [Rule]. |
376 /// | 366 /// |
377 /// If omitted, defaults to a new [SimpleRule]. | 367 /// If omitted, defaults to a new [Rule]. |
378 void startRule([Rule rule]) { | 368 void startRule([Rule rule]) { |
379 if (rule == null) rule = new SimpleRule(); | 369 if (rule == null) rule = new Rule(); |
380 | 370 |
381 // See if any of the rules that contain this one care if it splits. | 371 // See if any of the rules that contain this one care if it splits. |
382 _rules.forEach((outer) => outer.contain(rule)); | 372 _rules.forEach((outer) => outer.contain(rule)); |
383 _rules.add(rule); | 373 _rules.add(rule); |
384 } | 374 } |
385 | 375 |
386 /// Starts a new [Rule] that comes into play *after* the next whitespace | 376 /// Starts a new [Rule] that comes into play *after* the next whitespace |
387 /// (including comments) is written. | 377 /// (including comments) is written. |
388 /// | 378 /// |
389 /// This is used for binary operators who want to start a rule before the | 379 /// This is used for binary operators who want to start a rule before the |
390 /// first operand but not get forced to split if a comment appears before the | 380 /// first operand but not get forced to split if a comment appears before the |
391 /// entire expression. | 381 /// entire expression. |
392 /// | 382 /// |
393 /// If [rule] is omitted, defaults to a new [SimpleRule]. | 383 /// If [rule] is omitted, defaults to a new [Rule]. |
394 void startLazyRule([Rule rule]) { | 384 void startLazyRule([Rule rule]) { |
395 if (rule == null) rule = new SimpleRule(); | 385 if (rule == null) rule = new Rule(); |
396 | 386 |
397 _lazyRules.add(rule); | 387 _lazyRules.add(rule); |
398 } | 388 } |
399 | 389 |
400 /// Ends the innermost rule. | 390 /// Ends the innermost rule. |
401 void endRule() { | 391 void endRule() { |
402 _rules.removeLast(); | 392 _rules.removeLast(); |
403 } | 393 } |
404 | 394 |
405 /// Pre-emptively forces all of the current rules to become hard splits. | 395 /// Pre-emptively forces all of the current rules to become hard splits. |
(...skipping 18 matching lines...) Expand all Loading... |
424 if (now == null) now = false; | 414 if (now == null) now = false; |
425 | 415 |
426 _nesting.nest(indent); | 416 _nesting.nest(indent); |
427 if (now) _nesting.commitNesting(); | 417 if (now) _nesting.commitNesting(); |
428 } | 418 } |
429 | 419 |
430 /// Discards the most recent level of expression nesting. | 420 /// Discards the most recent level of expression nesting. |
431 /// | 421 /// |
432 /// Expressions that are more nested will get increased indentation when split | 422 /// Expressions that are more nested will get increased indentation when split |
433 /// if the previous line has a lower level of nesting. | 423 /// if the previous line has a lower level of nesting. |
434 void unnest() { | 424 /// |
| 425 /// If [now] is `false`, does not commit the nesting change until after the |
| 426 /// next chunk of text is written. |
| 427 void unnest({bool now}) { |
| 428 if (now == null) now = true; |
| 429 |
435 _nesting.unnest(); | 430 _nesting.unnest(); |
| 431 if (now) _nesting.commitNesting(); |
436 } | 432 } |
437 | 433 |
438 /// Marks the selection starting point as occurring [fromEnd] characters to | 434 /// Marks the selection starting point as occurring [fromEnd] characters to |
439 /// the left of the end of what's currently been written. | 435 /// the left of the end of what's currently been written. |
440 /// | 436 /// |
441 /// It counts backwards from the end because this is called *after* the chunk | 437 /// It counts backwards from the end because this is called *after* the chunk |
442 /// of text containing the selection has been output. | 438 /// of text containing the selection has been output. |
443 void startSelectionFromEnd(int fromEnd) { | 439 void startSelectionFromEnd(int fromEnd) { |
444 assert(_chunks.isNotEmpty); | 440 assert(_chunks.isNotEmpty); |
445 _chunks.last.startSelectionFromEnd(fromEnd); | 441 _chunks.last.startSelectionFromEnd(fromEnd); |
(...skipping 16 matching lines...) Expand all Loading... |
462 } | 458 } |
463 | 459 |
464 /// Releases the last nesting level captured by [startBlockArgumentNesting]. | 460 /// Releases the last nesting level captured by [startBlockArgumentNesting]. |
465 void endBlockArgumentNesting() { | 461 void endBlockArgumentNesting() { |
466 _blockArgumentNesting.removeLast(); | 462 _blockArgumentNesting.removeLast(); |
467 } | 463 } |
468 | 464 |
469 /// Starts a new block as a child of the current chunk. | 465 /// Starts a new block as a child of the current chunk. |
470 /// | 466 /// |
471 /// Nested blocks are handled using their own independent [LineWriter]. | 467 /// Nested blocks are handled using their own independent [LineWriter]. |
472 ChunkBuilder startBlock() { | 468 ChunkBuilder startBlock(Chunk argumentChunk) { |
| 469 var chunk = _chunks.last; |
| 470 chunk.makeBlock(argumentChunk); |
| 471 |
473 var builder = | 472 var builder = |
474 new ChunkBuilder._(this, _formatter, _source, _chunks.last.blockChunks); | 473 new ChunkBuilder._(this, _formatter, _source, chunk.block.chunks); |
475 | 474 |
476 // A block always starts off indented one level. | 475 // A block always starts off indented one level. |
477 builder.indent(); | 476 builder.indent(); |
478 | 477 |
479 return builder; | 478 return builder; |
480 } | 479 } |
481 | 480 |
482 /// Ends this [ChunkBuilder], which must have been created by [startBlock()]. | 481 /// Ends this [ChunkBuilder], which must have been created by [startBlock()]. |
483 /// | 482 /// |
484 /// Forces the chunk that owns the block to split if it can tell that the | 483 /// Forces the chunk that owns the block to split if it can tell that the |
485 /// block contents will always split. It does that by looking for hard splits | 484 /// block contents will always split. It does that by looking for hard splits |
486 /// in the block. If [ignoredSplit] is given, that rule will be ignored | 485 /// in the block. If [ignoredSplit] is given, that rule will be ignored |
487 /// when determining if a block contains a hard split. If [forceSplit] is | 486 /// when determining if a block contains a hard split. If [forceSplit] is |
488 /// `true`, the block is considered to always split. | 487 /// `true`, the block is considered to always split. |
489 /// | 488 /// |
490 /// Returns the previous writer for the surrounding block. | 489 /// Returns the previous writer for the surrounding block. |
491 ChunkBuilder endBlock(HardSplitRule ignoredSplit, {bool forceSplit}) { | 490 ChunkBuilder endBlock(Rule ignoredSplit, {bool forceSplit}) { |
492 _divideChunks(); | 491 _divideChunks(); |
493 | 492 |
494 // If we don't already know if the block is going to split, see if it | 493 // If we don't already know if the block is going to split, see if it |
495 // contains any hard splits or is longer than a page. | 494 // contains any hard splits or is longer than a page. |
496 if (!forceSplit) { | 495 if (!forceSplit) { |
497 var length = 0; | 496 var length = 0; |
498 for (var chunk in _chunks) { | 497 for (var chunk in _chunks) { |
499 length += chunk.length + chunk.unsplitBlockLength; | 498 length += chunk.length + chunk.unsplitBlockLength; |
500 if (length > _formatter.pageWidth) { | 499 if (length > _formatter.pageWidth) { |
501 forceSplit = true; | 500 forceSplit = true; |
502 break; | 501 break; |
503 } | 502 } |
504 | 503 |
505 if (chunk.isHardSplit && chunk.rule != ignoredSplit) { | 504 if (chunk.rule != null && |
| 505 chunk.rule.isHardened && |
| 506 chunk.rule != ignoredSplit) { |
506 forceSplit = true; | 507 forceSplit = true; |
507 break; | 508 break; |
508 } | 509 } |
509 } | 510 } |
510 } | 511 } |
511 | 512 |
512 // If there is a hard newline within the block, force the surrounding rule | 513 _parent._endChildBlock( |
513 // for it so that we apply that constraint. | 514 firstFlushLeft: _firstFlushLeft, forceSplit: forceSplit); |
514 if (forceSplit) _parent.forceRules(); | |
515 | 515 |
516 // Write the split for the block contents themselves. | |
517 _parent._writeSplit(_parent._rules.last, _parent._blockArgumentNesting.last, | |
518 flushLeft: _firstFlushLeft); | |
519 return _parent; | 516 return _parent; |
520 } | 517 } |
521 | 518 |
| 519 /// Finishes off the last chunk in a child block of this parent. |
| 520 void _endChildBlock({bool firstFlushLeft, bool forceSplit}) { |
| 521 // If there is a hard newline within the block, force the surrounding rule |
| 522 // for it so that we apply that constraint. |
| 523 if (forceSplit) forceRules(); |
| 524 |
| 525 // Write the split for the block contents themselves. |
| 526 var chunk = _chunks.last; |
| 527 chunk.applySplit(rule, _nesting.indentation, _blockArgumentNesting.last, |
| 528 flushLeft: firstFlushLeft); |
| 529 |
| 530 if (chunk.rule.isHardened) _handleHardSplit(); |
| 531 } |
| 532 |
522 /// Finishes writing and returns a [SourceCode] containing the final output | 533 /// Finishes writing and returns a [SourceCode] containing the final output |
523 /// and updated selection, if any. | 534 /// and updated selection, if any. |
524 SourceCode end() { | 535 SourceCode end() { |
525 _writeHardSplit(); | 536 _writeHardSplit(); |
526 _divideChunks(); | 537 _divideChunks(); |
527 | 538 |
528 if (debug.traceChunkBuilder) { | 539 if (debug.traceChunkBuilder) { |
529 debug.log(debug.green("\nBuilt:")); | 540 debug.log(debug.green("\nBuilt:")); |
530 debug.dumpChunks(0, _chunks); | 541 debug.dumpChunks(0, _chunks); |
531 debug.log(); | 542 debug.log(); |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
570 | 581 |
571 case Whitespace.newline: | 582 case Whitespace.newline: |
572 _writeHardSplit(); | 583 _writeHardSplit(); |
573 break; | 584 break; |
574 | 585 |
575 case Whitespace.nestedNewline: | 586 case Whitespace.nestedNewline: |
576 _writeHardSplit(nest: true); | 587 _writeHardSplit(nest: true); |
577 break; | 588 break; |
578 | 589 |
579 case Whitespace.newlineFlushLeft: | 590 case Whitespace.newlineFlushLeft: |
580 _writeHardSplit(nest: true, flushLeft: true); | 591 _writeHardSplit(flushLeft: true, nest: true); |
581 break; | 592 break; |
582 | 593 |
583 case Whitespace.twoNewlines: | 594 case Whitespace.twoNewlines: |
584 _writeHardSplit(double: true); | 595 _writeHardSplit(isDouble: true); |
585 break; | 596 break; |
586 | 597 |
587 case Whitespace.spaceOrNewline: | 598 case Whitespace.spaceOrNewline: |
588 case Whitespace.splitOrNewline: | 599 case Whitespace.splitOrNewline: |
589 case Whitespace.oneOrTwoNewlines: | 600 case Whitespace.oneOrTwoNewlines: |
590 // We should have pinned these down before getting here. | 601 // We should have pinned these down before getting here. |
591 assert(false); | 602 assert(false); |
592 break; | 603 break; |
593 } | 604 } |
594 | 605 |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
654 token != "]" && | 665 token != "]" && |
655 token != "}" && | 666 token != "}" && |
656 token != "," && | 667 token != "," && |
657 token != ";" && | 668 token != ";" && |
658 token != ""; | 669 token != ""; |
659 } | 670 } |
660 | 671 |
661 /// Appends a hard split with the current indentation and nesting (the latter | 672 /// Appends a hard split with the current indentation and nesting (the latter |
662 /// only if [nest] is `true`). | 673 /// only if [nest] is `true`). |
663 /// | 674 /// |
664 /// If [double] is `true` or `false`, forces a since or double line to be | 675 /// If [double] is `true` or `false`, forces a single or double line to be |
665 /// output. Otherwise, it is left indeterminate. | 676 /// output. Otherwise, it is left indeterminate. |
666 /// | 677 /// |
667 /// If [flushLeft] is `true`, then the split will always cause the next line | 678 /// If [flushLeft] is `true`, then the split will always cause the next line |
668 /// to be at column zero. Otherwise, it uses the normal indentation and | 679 /// to be at column zero. Otherwise, it uses the normal indentation and |
669 /// nesting behavior. | 680 /// nesting behavior. |
670 void _writeHardSplit({bool nest: false, bool double, bool flushLeft}) { | 681 void _writeHardSplit({bool isDouble, bool flushLeft, bool nest: false}) { |
671 // A hard split overrides any other whitespace. | 682 // A hard split overrides any other whitespace. |
672 _pendingWhitespace = null; | 683 _pendingWhitespace = null; |
673 _writeSplit(new HardSplitRule(), nest ? null : _nesting.blockNesting, | 684 _writeSplit(new Rule.hard(), |
674 flushLeft: flushLeft, isDouble: double); | 685 flushLeft: flushLeft, isDouble: isDouble, nest: nest); |
675 } | 686 } |
676 | 687 |
677 /// Ends the current chunk (if any) with the given split information. | 688 /// Ends the current chunk (if any) with the given split information. |
678 /// | 689 /// |
679 /// Returns the chunk. | 690 /// Returns the chunk. |
680 Chunk _writeSplit(Rule rule, NestingLevel nesting, | 691 Chunk _writeSplit(Rule rule, |
681 {bool flushLeft, bool isDouble, bool spaceWhenUnsplit}) { | 692 {bool flushLeft, bool isDouble, bool nest, bool space}) { |
| 693 if (nest == null) nest = true; |
| 694 |
682 if (_chunks.isEmpty) { | 695 if (_chunks.isEmpty) { |
683 if (flushLeft != null) _firstFlushLeft = flushLeft; | 696 if (flushLeft != null) _firstFlushLeft = flushLeft; |
684 | 697 |
685 return null; | 698 return null; |
686 } | 699 } |
687 | 700 |
688 if (nesting == null) nesting = _nesting.nesting; | 701 _chunks.last.applySplit(rule, _nesting.indentation, |
| 702 nest ? _nesting.nesting : new NestingLevel(), |
| 703 flushLeft: flushLeft, isDouble: isDouble, space: space); |
689 | 704 |
690 var chunk = _chunks.last; | 705 if (_chunks.last.rule.isHardened) _handleHardSplit(); |
691 chunk.applySplit(rule, _nesting.indentation, nesting, | 706 return _chunks.last; |
692 flushLeft: flushLeft, | |
693 isDouble: isDouble, | |
694 spaceWhenUnsplit: spaceWhenUnsplit); | |
695 | |
696 // Keep track of which chunks are owned by the rule. | |
697 if (rule is! HardSplitRule) { | |
698 _ruleChunks.putIfAbsent(rule, () => []).add(_chunks.length - 1); | |
699 } | |
700 | |
701 if (chunk.isHardSplit) _handleHardSplit(); | |
702 | |
703 return chunk; | |
704 } | 707 } |
705 | 708 |
706 /// Writes [text] to either the current chunk or a new one if the current | 709 /// Writes [text] to either the current chunk or a new one if the current |
707 /// chunk is complete. | 710 /// chunk is complete. |
708 void _writeText(String text) { | 711 void _writeText(String text) { |
709 if (_chunks.isNotEmpty && _chunks.last.canAddText) { | 712 if (_chunks.isNotEmpty && _chunks.last.canAddText) { |
710 _chunks.last.appendText(text); | 713 _chunks.last.appendText(text); |
711 } else { | 714 } else { |
712 _chunks.add(new Chunk(text)); | 715 _chunks.add(new Chunk(text)); |
713 } | 716 } |
714 } | 717 } |
715 | 718 |
716 /// Returns true if we can divide the chunks at [index] and line split the | 719 /// Returns true if we can divide the chunks at [index] and line split the |
717 /// ones before and after that separately. | 720 /// ones before and after that separately. |
718 bool _canDivideAt(int i) { | 721 bool _canDivideAt(int i) { |
| 722 // Don't divide after the last chunk. |
| 723 if (i == _chunks.length - 1) return false; |
| 724 |
719 var chunk = _chunks[i]; | 725 var chunk = _chunks[i]; |
720 if (!chunk.isHardSplit) return false; | 726 if (!chunk.rule.isHardened) return false; |
721 if (chunk.nesting.isNested) return false; | 727 if (chunk.nesting.isNested) return false; |
722 if (chunk.blockChunks.isNotEmpty) return false; | 728 if (chunk.isBlock) return false; |
723 | |
724 // Make sure we don't split the line in the middle of a rule. | |
725 var chunks = _ruleChunks[chunk.rule]; | |
726 if (chunks != null && chunks.any((other) => other > i)) return false; | |
727 | 729 |
728 return true; | 730 return true; |
729 } | 731 } |
730 | 732 |
731 /// Pre-processes the chunks after they are done being written by the visitor | 733 /// Pre-processes the chunks after they are done being written by the visitor |
732 /// but before they are run through the line splitter. | 734 /// but before they are run through the line splitter. |
733 /// | 735 /// |
734 /// Marks ranges of chunks that can be line split independently to keep the | 736 /// Marks ranges of chunks that can be line split independently to keep the |
735 /// batches we send to [LineSplitter] small. | 737 /// batches we send to [LineSplitter] small. |
736 void _divideChunks() { | 738 void _divideChunks() { |
(...skipping 21 matching lines...) Expand all Loading... |
758 _hardSplitRules.add(_rules.last); | 760 _hardSplitRules.add(_rules.last); |
759 } | 761 } |
760 | 762 |
761 /// Replaces all of the previously hardened rules with hard splits, along | 763 /// Replaces all of the previously hardened rules with hard splits, along |
762 /// with every rule that those constrain to also split. | 764 /// with every rule that those constrain to also split. |
763 /// | 765 /// |
764 /// This should only be called after all chunks have been written. | 766 /// This should only be called after all chunks have been written. |
765 void _hardenRules() { | 767 void _hardenRules() { |
766 if (_hardSplitRules.isEmpty) return; | 768 if (_hardSplitRules.isEmpty) return; |
767 | 769 |
768 // Harden all of the rules that are constrained by [rules] as well. | |
769 var hardenedRules = new Set(); | |
770 walkConstraints(rule) { | 770 walkConstraints(rule) { |
771 if (hardenedRules.contains(rule)) return; | 771 rule.harden(); |
772 hardenedRules.add(rule); | |
773 | 772 |
774 // Follow this rule's constraints, recursively. | 773 // Follow this rule's constraints, recursively. |
775 for (var other in _ruleChunks.keys) { | 774 for (var other in rule.constrainedRules) { |
776 if (other == rule) continue; | 775 if (other == rule) continue; |
777 | 776 |
778 if (rule.constrain(rule.fullySplitValue, other) == | 777 if (!other.isHardened && |
779 other.fullySplitValue) { | 778 rule.constrain(rule.fullySplitValue, other) == |
| 779 other.fullySplitValue) { |
780 walkConstraints(other); | 780 walkConstraints(other); |
781 } | 781 } |
782 } | 782 } |
783 } | 783 } |
784 | 784 |
785 for (var rule in _hardSplitRules) { | 785 for (var rule in _hardSplitRules) { |
786 walkConstraints(rule); | 786 walkConstraints(rule); |
787 } | 787 } |
788 | 788 |
789 // Harden every chunk that uses one of these rules. | 789 // Discard spans in hardened chunks since we know for certain they will |
| 790 // split anyway. |
790 for (var chunk in _chunks) { | 791 for (var chunk in _chunks) { |
791 if (hardenedRules.contains(chunk.rule)) { | 792 if (chunk.rule != null && chunk.rule.isHardened) { |
792 chunk.harden(); | 793 chunk.spans.clear(); |
793 } | 794 } |
794 } | 795 } |
795 } | 796 } |
796 } | 797 } |
OLD | NEW |