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 343 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
354 var openSpan = _openSpans.removeLast(); | 354 var openSpan = _openSpans.removeLast(); |
355 | 355 |
356 // A span that just covers a single chunk can't be split anyway. | 356 // A span that just covers a single chunk can't be split anyway. |
357 var end = _currentChunkIndex; | 357 var end = _currentChunkIndex; |
358 if (openSpan.start == end) return; | 358 if (openSpan.start == end) return; |
359 | 359 |
360 // Add the span to every chunk that can split it. | 360 // Add the span to every chunk that can split it. |
361 var span = new Span(openSpan.cost); | 361 var span = new Span(openSpan.cost); |
362 for (var i = openSpan.start; i < end; i++) { | 362 for (var i = openSpan.start; i < end; i++) { |
363 var chunk = _chunks[i]; | 363 var chunk = _chunks[i]; |
364 if (!chunk.isHardSplit) chunk.spans.add(span); | 364 if (!chunk.rule.isHardened) chunk.spans.add(span); |
365 } | 365 } |
366 } | 366 } |
367 | 367 |
368 /// Starts a new [Rule]. | 368 /// Starts a new [Rule]. |
369 /// | 369 /// |
370 /// If omitted, defaults to a new [SimpleRule]. | 370 /// If omitted, defaults to a new [Rule]. |
371 void startRule([Rule rule]) { | 371 void startRule([Rule rule]) { |
372 if (rule == null) rule = new SimpleRule(); | 372 if (rule == null) rule = new Rule(); |
373 | 373 |
374 // See if any of the rules that contain this one care if it splits. | 374 // See if any of the rules that contain this one care if it splits. |
375 _rules.forEach((outer) => outer.contain(rule)); | 375 _rules.forEach((outer) => outer.contain(rule)); |
376 _rules.add(rule); | 376 _rules.add(rule); |
377 } | 377 } |
378 | 378 |
379 /// Starts a new [Rule] that comes into play *after* the next whitespace | 379 /// Starts a new [Rule] that comes into play *after* the next whitespace |
380 /// (including comments) is written. | 380 /// (including comments) is written. |
381 /// | 381 /// |
382 /// This is used for binary operators who want to start a rule before the | 382 /// This is used for binary operators who want to start a rule before the |
383 /// first operand but not get forced to split if a comment appears before the | 383 /// first operand but not get forced to split if a comment appears before the |
384 /// entire expression. | 384 /// entire expression. |
385 /// | 385 /// |
386 /// If [rule] is omitted, defaults to a new [SimpleRule]. | 386 /// If [rule] is omitted, defaults to a new [Rule]. |
387 void startLazyRule([Rule rule]) { | 387 void startLazyRule([Rule rule]) { |
388 if (rule == null) rule = new SimpleRule(); | 388 if (rule == null) rule = new Rule(); |
389 | 389 |
390 _lazyRules.add(rule); | 390 _lazyRules.add(rule); |
391 } | 391 } |
392 | 392 |
393 /// Ends the innermost rule. | 393 /// Ends the innermost rule. |
394 void endRule() { | 394 void endRule() { |
395 _rules.removeLast(); | 395 _rules.removeLast(); |
396 } | 396 } |
397 | 397 |
398 /// Pre-emptively forces all of the current rules to become hard splits. | 398 /// Pre-emptively forces all of the current rules to become hard splits. |
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
483 | 483 |
484 /// Ends this [ChunkBuilder], which must have been created by [startBlock()]. | 484 /// Ends this [ChunkBuilder], which must have been created by [startBlock()]. |
485 /// | 485 /// |
486 /// Forces the chunk that owns the block to split if it can tell that the | 486 /// Forces the chunk that owns the block to split if it can tell that the |
487 /// block contents will always split. It does that by looking for hard splits | 487 /// block contents will always split. It does that by looking for hard splits |
488 /// in the block. If [ignoredSplit] is given, that rule will be ignored | 488 /// in the block. If [ignoredSplit] is given, that rule will be ignored |
489 /// when determining if a block contains a hard split. If [forceSplit] is | 489 /// when determining if a block contains a hard split. If [forceSplit] is |
490 /// `true`, the block is considered to always split. | 490 /// `true`, the block is considered to always split. |
491 /// | 491 /// |
492 /// Returns the previous writer for the surrounding block. | 492 /// Returns the previous writer for the surrounding block. |
493 ChunkBuilder endBlock(HardSplitRule ignoredSplit, {bool forceSplit}) { | 493 ChunkBuilder endBlock(Rule ignoredSplit, {bool forceSplit}) { |
494 _divideChunks(); | 494 _divideChunks(); |
495 | 495 |
496 // If we don't already know if the block is going to split, see if it | 496 // If we don't already know if the block is going to split, see if it |
497 // contains any hard splits or is longer than a page. | 497 // contains any hard splits or is longer than a page. |
498 if (!forceSplit) { | 498 if (!forceSplit) { |
499 var length = 0; | 499 var length = 0; |
500 for (var chunk in _chunks) { | 500 for (var chunk in _chunks) { |
501 length += chunk.length + chunk.unsplitBlockLength; | 501 length += chunk.length + chunk.unsplitBlockLength; |
502 if (length > _formatter.pageWidth) { | 502 if (length > _formatter.pageWidth) { |
503 forceSplit = true; | 503 forceSplit = true; |
504 break; | 504 break; |
505 } | 505 } |
506 | 506 |
507 if (chunk.isHardSplit && chunk.rule != ignoredSplit) { | 507 if (chunk.rule != null && |
| 508 chunk.rule.isHardened && |
| 509 chunk.rule != ignoredSplit) { |
508 forceSplit = true; | 510 forceSplit = true; |
509 break; | 511 break; |
510 } | 512 } |
511 } | 513 } |
512 } | 514 } |
513 | 515 |
514 _parent._endChildBlock( | 516 _parent._endChildBlock( |
515 firstFlushLeft: _firstFlushLeft, forceSplit: forceSplit); | 517 firstFlushLeft: _firstFlushLeft, forceSplit: forceSplit); |
516 | 518 |
517 return _parent; | 519 return _parent; |
(...skipping 157 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
675 /// | 677 /// |
676 /// If [double] is `true` or `false`, forces a single or double line to be | 678 /// If [double] is `true` or `false`, forces a single or double line to be |
677 /// output. Otherwise, it is left indeterminate. | 679 /// output. Otherwise, it is left indeterminate. |
678 /// | 680 /// |
679 /// If [flushLeft] is `true`, then the split will always cause the next line | 681 /// If [flushLeft] is `true`, then the split will always cause the next line |
680 /// to be at column zero. Otherwise, it uses the normal indentation and | 682 /// to be at column zero. Otherwise, it uses the normal indentation and |
681 /// nesting behavior. | 683 /// nesting behavior. |
682 void _writeHardSplit({bool isDouble, bool flushLeft, bool nest: false}) { | 684 void _writeHardSplit({bool isDouble, bool flushLeft, bool nest: false}) { |
683 // A hard split overrides any other whitespace. | 685 // A hard split overrides any other whitespace. |
684 _pendingWhitespace = null; | 686 _pendingWhitespace = null; |
685 _writeSplit(new HardSplitRule(), | 687 _writeSplit(new Rule.hard(), |
686 flushLeft: flushLeft, isDouble: isDouble, nest: nest); | 688 flushLeft: flushLeft, isDouble: isDouble, nest: nest); |
687 } | 689 } |
688 | 690 |
689 /// Ends the current chunk (if any) with the given split information. | 691 /// Ends the current chunk (if any) with the given split information. |
690 /// | 692 /// |
691 /// Returns the chunk. | 693 /// Returns the chunk. |
692 Chunk _writeSplit(Rule rule, | 694 Chunk _writeSplit(Rule rule, |
693 {bool flushLeft, bool isDouble, bool nest, bool space}) { | 695 {bool flushLeft, bool isDouble, bool nest, bool space}) { |
694 if (nest == null) nest = true; | 696 if (nest == null) nest = true; |
695 | 697 |
696 if (_chunks.isEmpty) { | 698 if (_chunks.isEmpty) { |
697 if (flushLeft != null) _firstFlushLeft = flushLeft; | 699 if (flushLeft != null) _firstFlushLeft = flushLeft; |
698 | 700 |
699 return null; | 701 return null; |
700 } | 702 } |
701 | 703 |
702 _chunks.last.applySplit(rule, _nesting.indentation, | 704 _chunks.last.applySplit(rule, _nesting.indentation, |
703 nest ? _nesting.nesting : new NestingLevel(), | 705 nest ? _nesting.nesting : new NestingLevel(), |
704 flushLeft: flushLeft, isDouble: isDouble, space: space); | 706 flushLeft: flushLeft, isDouble: isDouble, space: space); |
705 | 707 |
706 return _afterSplit(); | 708 return _afterSplit(); |
707 } | 709 } |
708 | 710 |
709 /// Keep tracks of which chunks are owned by which rules and handles hard | 711 /// Keep tracks of which chunks are owned by which rules and handles hard |
710 /// splits after a chunk has been completed. | 712 /// splits after a chunk has been completed. |
711 Chunk _afterSplit() { | 713 Chunk _afterSplit() { |
712 var chunk = _chunks.last; | 714 var chunk = _chunks.last; |
713 | 715 |
714 if (chunk.rule is! HardSplitRule) { | 716 if (_rules.isNotEmpty) { |
715 _ruleChunks.putIfAbsent(rule, () => []).add(_chunks.length - 1); | 717 _ruleChunks.putIfAbsent(rule, () => []).add(_chunks.length - 1); |
716 } | 718 } |
717 | 719 |
718 if (chunk.isHardSplit) _handleHardSplit(); | 720 if (chunk.rule.isHardened) _handleHardSplit(); |
719 | 721 |
720 return chunk; | 722 return chunk; |
721 } | 723 } |
722 | 724 |
723 /// Writes [text] to either the current chunk or a new one if the current | 725 /// Writes [text] to either the current chunk or a new one if the current |
724 /// chunk is complete. | 726 /// chunk is complete. |
725 void _writeText(String text) { | 727 void _writeText(String text) { |
726 if (_chunks.isNotEmpty && _chunks.last.canAddText) { | 728 if (_chunks.isNotEmpty && _chunks.last.canAddText) { |
727 _chunks.last.appendText(text); | 729 _chunks.last.appendText(text); |
728 } else { | 730 } else { |
729 _chunks.add(new Chunk(text)); | 731 _chunks.add(new Chunk(text)); |
730 } | 732 } |
731 } | 733 } |
732 | 734 |
733 /// Returns true if we can divide the chunks at [index] and line split the | 735 /// Returns true if we can divide the chunks at [index] and line split the |
734 /// ones before and after that separately. | 736 /// ones before and after that separately. |
735 bool _canDivideAt(int i) { | 737 bool _canDivideAt(int i) { |
| 738 // Don't divide after the last chunk. |
| 739 if (i == _chunks.length - 1) return false; |
| 740 |
736 var chunk = _chunks[i]; | 741 var chunk = _chunks[i]; |
737 if (!chunk.isHardSplit) return false; | 742 if (!chunk.rule.isHardened) return false; |
738 if (chunk.nesting.isNested) return false; | 743 if (chunk.nesting.isNested) return false; |
739 if (chunk.isBlock) return false; | 744 if (chunk.isBlock) return false; |
740 | 745 |
741 // Make sure we don't split the line in the middle of a rule. | 746 // Make sure we don't split the line in the middle of a rule. |
742 var chunks = _ruleChunks[chunk.rule]; | 747 var chunks = _ruleChunks[chunk.rule]; |
743 if (chunks != null && chunks.any((other) => other > i)) return false; | 748 if (chunks != null && chunks.any((other) => other > i)) return false; |
744 | 749 |
745 return true; | 750 return true; |
746 } | 751 } |
747 | 752 |
(...skipping 27 matching lines...) Expand all Loading... |
775 _hardSplitRules.add(_rules.last); | 780 _hardSplitRules.add(_rules.last); |
776 } | 781 } |
777 | 782 |
778 /// Replaces all of the previously hardened rules with hard splits, along | 783 /// Replaces all of the previously hardened rules with hard splits, along |
779 /// with every rule that those constrain to also split. | 784 /// with every rule that those constrain to also split. |
780 /// | 785 /// |
781 /// This should only be called after all chunks have been written. | 786 /// This should only be called after all chunks have been written. |
782 void _hardenRules() { | 787 void _hardenRules() { |
783 if (_hardSplitRules.isEmpty) return; | 788 if (_hardSplitRules.isEmpty) return; |
784 | 789 |
785 // Harden all of the rules that are constrained by [rules] as well. | |
786 var hardenedRules = new Set(); | |
787 walkConstraints(rule) { | 790 walkConstraints(rule) { |
788 if (hardenedRules.contains(rule)) return; | 791 rule.harden(); |
789 hardenedRules.add(rule); | |
790 | 792 |
791 // Follow this rule's constraints, recursively. | 793 // Follow this rule's constraints, recursively. |
792 for (var other in _ruleChunks.keys) { | 794 for (var other in _ruleChunks.keys) { |
793 if (other == rule) continue; | 795 if (other == rule) continue; |
794 | 796 |
795 if (rule.constrain(rule.fullySplitValue, other) == | 797 if (!other.isHardened && |
796 other.fullySplitValue) { | 798 rule.constrain(rule.fullySplitValue, other) == |
| 799 other.fullySplitValue) { |
797 walkConstraints(other); | 800 walkConstraints(other); |
798 } | 801 } |
799 } | 802 } |
800 } | 803 } |
801 | 804 |
802 for (var rule in _hardSplitRules) { | 805 for (var rule in _hardSplitRules) { |
803 walkConstraints(rule); | 806 walkConstraints(rule); |
804 } | 807 } |
805 | 808 |
806 // Harden every chunk that uses one of these rules. | 809 // Discard spans in hardened chunks since we know for certain they will |
| 810 // split anyway. |
807 for (var chunk in _chunks) { | 811 for (var chunk in _chunks) { |
808 if (hardenedRules.contains(chunk.rule)) { | 812 if (chunk.rule != null && chunk.rule.isHardened) { |
809 chunk.harden(); | 813 chunk.spans.clear(); |
810 } | 814 } |
811 } | 815 } |
812 } | 816 } |
813 } | 817 } |
OLD | NEW |