| 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.source_writer; | 5 library dart_style.src.source_writer; |
| 6 | 6 |
| 7 import 'dart_formatter.dart'; | 7 import 'dart_formatter.dart'; |
| 8 import 'chunk.dart'; | 8 import 'chunk.dart'; |
| 9 import 'debug.dart'; | 9 import 'debug.dart'; |
| 10 import 'line_splitter.dart'; | 10 import 'line_splitter.dart'; |
| 11 import 'multisplit.dart'; | 11 import 'multisplit.dart'; |
| 12 import 'whitespace.dart'; | 12 import 'whitespace.dart'; |
| 13 | 13 |
| 14 /// A comment in the source, with a bit of information about the surrounding | |
| 15 /// whitespace. | |
| 16 class SourceComment { | |
| 17 /// The text of the comment, including `//`, `/*`, and `*/`. | |
| 18 final String text; | |
| 19 | |
| 20 /// The number of newlines between the comment or token preceding this comment | |
| 21 /// and the beginning of this one. | |
| 22 /// | |
| 23 /// Will be zero if the comment is a trailing one. | |
| 24 final int linesBefore; | |
| 25 | |
| 26 /// Whether this comment is a line comment. | |
| 27 final bool isLineComment; | |
| 28 | |
| 29 /// Whether this comment starts at column one in the source. | |
| 30 /// | |
| 31 /// Comments that start at the start of the line will not be indented in the | |
| 32 /// output. This way, commented out chunks of code do not get erroneously | |
| 33 /// re-indented. | |
| 34 final bool isStartOfLine; | |
| 35 | |
| 36 SourceComment(this.text, this.linesBefore, | |
| 37 {this.isLineComment, this.isStartOfLine}); | |
| 38 } | |
| 39 | |
| 40 /// Takes the incremental serialized output of [SourceVisitor]--the source text | 14 /// Takes the incremental serialized output of [SourceVisitor]--the source text |
| 41 /// along with any comments and preserved whitespace--and produces a coherent | 15 /// along with any comments and preserved whitespace--and produces a coherent |
| 42 /// series of [Chunk]s which can then be split into physical lines. | 16 /// series of [Chunk]s which can then be split into physical lines. |
| 43 /// | 17 /// |
| 44 /// Keeps track of leading indentation, expression nesting, and all of the hairy | 18 /// Keeps track of leading indentation, expression nesting, and all of the hairy |
| 45 /// code required to seamlessly integrate existing comments into the pure | 19 /// code required to seamlessly integrate existing comments into the pure |
| 46 /// output produced by [SourceVisitor]. | 20 /// output produced by [SourceVisitor]. |
| 47 class LineWriter { | 21 class LineWriter { |
| 48 final DartFormatter _formatter; | 22 final DartFormatter _formatter; |
| 49 | 23 |
| 50 final StringBuffer _buffer; | 24 final SourceCode _source; |
| 25 |
| 26 final _buffer = new StringBuffer(); |
| 51 | 27 |
| 52 final _chunks = <Chunk>[]; | 28 final _chunks = <Chunk>[]; |
| 53 | 29 |
| 54 /// The whitespace that should be written to [_chunks] before the next | 30 /// The whitespace that should be written to [_chunks] before the next |
| 55 /// non-whitespace token or `null` if no whitespace is pending. | 31 /// non-whitespace token or `null` if no whitespace is pending. |
| 56 /// | 32 /// |
| 57 /// This ensures that changes to indentation and nesting also apply to the | 33 /// This ensures that changes to indentation and nesting also apply to the |
| 58 /// most recent split, even if the visitor "creates" the split before changing | 34 /// most recent split, even if the visitor "creates" the split before changing |
| 59 /// indentation or nesting. | 35 /// indentation or nesting. |
| 60 Whitespace _pendingWhitespace; | 36 Whitespace _pendingWhitespace; |
| (...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 154 /// The index of the "current" chunk being written. | 130 /// The index of the "current" chunk being written. |
| 155 /// | 131 /// |
| 156 /// If the last chunk is still being appended to, this is its index. | 132 /// If the last chunk is still being appended to, this is its index. |
| 157 /// Otherwise, it is the index of the next chunk which will be created. | 133 /// Otherwise, it is the index of the next chunk which will be created. |
| 158 int get _currentChunkIndex { | 134 int get _currentChunkIndex { |
| 159 if (_chunks.isEmpty) return 0; | 135 if (_chunks.isEmpty) return 0; |
| 160 if (_chunks.last.canAddText) return _chunks.length - 1; | 136 if (_chunks.last.canAddText) return _chunks.length - 1; |
| 161 return _chunks.length; | 137 return _chunks.length; |
| 162 } | 138 } |
| 163 | 139 |
| 140 /// The offset in [_buffer] where the selection starts in the formatted code. |
| 141 /// |
| 142 /// This will be `null` if there is no selection or the writer hasn't reached |
| 143 /// the beginning of the selection yet. |
| 144 int _selectionStart; |
| 145 |
| 146 /// The length in [_buffer] of the selection in the formatted code. |
| 147 /// |
| 148 /// This will be `null` if there is no selection or the writer hasn't reached |
| 149 /// the end of the selection yet. |
| 150 int _selectionLength; |
| 151 |
| 164 /// Whether there is pending whitespace that depends on the number of | 152 /// Whether there is pending whitespace that depends on the number of |
| 165 /// newlines in the source. | 153 /// newlines in the source. |
| 166 /// | 154 /// |
| 167 /// This is used to avoid calculating the newlines between tokens unless | 155 /// This is used to avoid calculating the newlines between tokens unless |
| 168 /// actually needed since doing so is slow when done between every single | 156 /// actually needed since doing so is slow when done between every single |
| 169 /// token pair. | 157 /// token pair. |
| 170 bool get needsToPreserveNewlines => | 158 bool get needsToPreserveNewlines => |
| 171 _pendingWhitespace == Whitespace.oneOrTwoNewlines || | 159 _pendingWhitespace == Whitespace.oneOrTwoNewlines || |
| 172 _pendingWhitespace == Whitespace.spaceOrNewline; | 160 _pendingWhitespace == Whitespace.spaceOrNewline; |
| 173 | 161 |
| 174 /// The number of characters of code that can fit in a single line. | 162 /// The number of characters of code that can fit in a single line. |
| 175 int get pageWidth => _formatter.pageWidth; | 163 int get pageWidth => _formatter.pageWidth; |
| 176 | 164 |
| 177 LineWriter(this._formatter, this._buffer) { | 165 LineWriter(this._formatter, this._source) { |
| 178 indent(_formatter.indent); | 166 indent(_formatter.indent); |
| 179 _beginningIndent = _formatter.indent; | 167 _beginningIndent = _formatter.indent; |
| 180 } | 168 } |
| 181 | 169 |
| 182 /// Writes [string], the text for a single token, to the output. | 170 /// Writes [string], the text for a single token, to the output. |
| 183 /// | 171 /// |
| 184 /// By default, this also implicitly adds one level of nesting if we aren't | 172 /// By default, this also implicitly adds one level of nesting if we aren't |
| 185 /// currently nested at all. We do this here so that if a comment appears | 173 /// currently nested at all. We do this here so that if a comment appears |
| 186 /// after any token within a statement or top-level form and that comment | 174 /// after any token within a statement or top-level form and that comment |
| 187 /// leads to splitting, we correctly nest. Even pathological cases like: | 175 /// leads to splitting, we correctly nest. Even pathological cases like: |
| (...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 291 _writeText(" "); | 279 _writeText(" "); |
| 292 } | 280 } |
| 293 } else { | 281 } else { |
| 294 // The comment starts a line, so make sure it stays on its own line. | 282 // The comment starts a line, so make sure it stays on its own line. |
| 295 _writeHardSplit(nest: true, allowIndent: !comment.isStartOfLine, | 283 _writeHardSplit(nest: true, allowIndent: !comment.isStartOfLine, |
| 296 double: comment.linesBefore > 1); | 284 double: comment.linesBefore > 1); |
| 297 } | 285 } |
| 298 | 286 |
| 299 _writeText(comment.text); | 287 _writeText(comment.text); |
| 300 | 288 |
| 289 if (comment.selectionStart != null) { |
| 290 startSelectionFromEnd(comment.text.length - comment.selectionStart); |
| 291 } |
| 292 |
| 293 if (comment.selectionEnd != null) { |
| 294 endSelectionFromEnd(comment.text.length - comment.selectionEnd); |
| 295 } |
| 296 |
| 301 // Make sure there is at least one newline after a line comment and allow | 297 // Make sure there is at least one newline after a line comment and allow |
| 302 // one or two after a block comment that has nothing after it. | 298 // one or two after a block comment that has nothing after it. |
| 303 var linesAfter; | 299 var linesAfter; |
| 304 if (i < comments.length - 1) { | 300 if (i < comments.length - 1) { |
| 305 linesAfter = comments[i + 1].linesBefore; | 301 linesAfter = comments[i + 1].linesBefore; |
| 306 } else { | 302 } else { |
| 307 linesAfter = linesBeforeToken; | 303 linesAfter = linesBeforeToken; |
| 308 | 304 |
| 309 // Always force a newline after multi-line block comments. Prevents | 305 // Always force a newline after multi-line block comments. Prevents |
| 310 // mistakes like: | 306 // mistakes like: |
| (...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 445 /// Expressions that are more nested will get increased indentation when split | 441 /// Expressions that are more nested will get increased indentation when split |
| 446 /// if the previous line has a lower level of nesting. | 442 /// if the previous line has a lower level of nesting. |
| 447 void unnest() { | 443 void unnest() { |
| 448 // By the time the nesting is done, it should have emitted some text and | 444 // By the time the nesting is done, it should have emitted some text and |
| 449 // not be pending anymore. | 445 // not be pending anymore. |
| 450 assert(_pendingNesting == null); | 446 assert(_pendingNesting == null); |
| 451 | 447 |
| 452 _nesting--; | 448 _nesting--; |
| 453 } | 449 } |
| 454 | 450 |
| 455 /// Finish writing the last line. | 451 /// Marks the selection starting point as occurring [fromEnd] characters to |
| 456 void end() { | 452 /// the left of the end of what's currently been written. |
| 453 /// |
| 454 /// It counts backwards from the end because this is called *after* the chunk |
| 455 /// of text containing the selection has been output. |
| 456 void startSelectionFromEnd(int fromEnd) { |
| 457 assert(_chunks.isNotEmpty); |
| 458 _chunks.last.startSelectionFromEnd(fromEnd); |
| 459 } |
| 460 |
| 461 /// Marks the selection ending point as occurring [fromEnd] characters to the |
| 462 /// left of the end of what's currently been written. |
| 463 /// |
| 464 /// It counts backwards from the end because this is called *after* the chunk |
| 465 /// of text containing the selection has been output. |
| 466 void endSelectionFromEnd(int fromEnd) { |
| 467 assert(_chunks.isNotEmpty); |
| 468 _chunks.last.endSelectionFromEnd(fromEnd); |
| 469 } |
| 470 |
| 471 /// Finishes writing and returns a [SourceCode] containing the final output |
| 472 /// and updated selection, if any. |
| 473 SourceCode end() { |
| 457 if (_chunks.isNotEmpty) _completeLine(); | 474 if (_chunks.isNotEmpty) _completeLine(); |
| 475 |
| 476 // Be a good citizen, end with a newline. |
| 477 if (_source.isCompilationUnit) _buffer.write(_formatter.lineEnding); |
| 478 |
| 479 // If we haven't hit the beginning and/or end of the selection yet, they |
| 480 // must be at the very end of the code. |
| 481 if (_source.selectionStart != null) { |
| 482 if (_selectionStart == null) { |
| 483 _selectionStart = _buffer.length; |
| 484 } |
| 485 |
| 486 if (_selectionLength == null) { |
| 487 _selectionLength = _buffer.length - _selectionStart; |
| 488 } |
| 489 } |
| 490 |
| 491 return new SourceCode(_buffer.toString(), |
| 492 uri: _source.uri, |
| 493 isCompilationUnit: _source.isCompilationUnit, |
| 494 selectionStart: _selectionStart, |
| 495 selectionLength: _selectionLength); |
| 458 } | 496 } |
| 459 | 497 |
| 460 /// Writes the current pending [Whitespace] to the output, if any. | 498 /// Writes the current pending [Whitespace] to the output, if any. |
| 461 /// | 499 /// |
| 462 /// This should only be called after source lines have been preserved to turn | 500 /// This should only be called after source lines have been preserved to turn |
| 463 /// any ambiguous whitespace into a concrete choice. | 501 /// any ambiguous whitespace into a concrete choice. |
| 464 void _emitPendingWhitespace() { | 502 void _emitPendingWhitespace() { |
| 465 if (_pendingWhitespace == null) return; | 503 if (_pendingWhitespace == null) return; |
| 466 // Output any pending whitespace first now that we know it won't be | 504 // Output any pending whitespace first now that we know it won't be |
| 467 // trailing. | 505 // trailing. |
| (...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 641 if (debugFormatter) { | 679 if (debugFormatter) { |
| 642 dumpChunks(_chunks); | 680 dumpChunks(_chunks); |
| 643 print(_spans.join("\n")); | 681 print(_spans.join("\n")); |
| 644 } | 682 } |
| 645 | 683 |
| 646 // Write the newlines required by the previous line. | 684 // Write the newlines required by the previous line. |
| 647 for (var i = 0; i < _bufferedNewlines; i++) { | 685 for (var i = 0; i < _bufferedNewlines; i++) { |
| 648 _buffer.write(_formatter.lineEnding); | 686 _buffer.write(_formatter.lineEnding); |
| 649 } | 687 } |
| 650 | 688 |
| 651 var splitter = new LineSplitter(_formatter.lineEnding, | 689 var splitter = new LineSplitter(_formatter.lineEnding, _formatter.pageWidth, |
| 652 _formatter.pageWidth, _chunks, _spans, _beginningIndent); | 690 _chunks, _spans, _beginningIndent); |
| 653 splitter.apply(_buffer); | 691 var selection = splitter.apply(_buffer); |
| 692 |
| 693 if (selection[0] != null) _selectionStart = selection[0]; |
| 694 if (selection[1] != null) _selectionLength = selection[1] - _selectionStart; |
| 654 } | 695 } |
| 655 | 696 |
| 656 /// Handles multisplits when a hard line occurs. | 697 /// Handles multisplits when a hard line occurs. |
| 657 /// | 698 /// |
| 658 /// Any active separable multisplits will get split in two at this point. | 699 /// Any active separable multisplits will get split in two at this point. |
| 659 /// Other multisplits are forced into the "hard" state. All of their previous | 700 /// Other multisplits are forced into the "hard" state. All of their previous |
| 660 /// splits are turned into explicit hard splits and any new splits for that | 701 /// splits are turned into explicit hard splits and any new splits for that |
| 661 /// multisplit become hard splits too. | 702 /// multisplit become hard splits too. |
| 662 void _splitMultisplits() { | 703 void _splitMultisplits() { |
| 663 if (_multisplits.isEmpty) return; | 704 if (_multisplits.isEmpty) return; |
| (...skipping 25 matching lines...) Expand all Loading... |
| 689 if (splitParams.contains(chunk.param)) { | 730 if (splitParams.contains(chunk.param)) { |
| 690 chunk.harden(); | 731 chunk.harden(); |
| 691 } else { | 732 } else { |
| 692 // If the chunk isn't hardened, but implies something that is, we can | 733 // If the chunk isn't hardened, but implies something that is, we can |
| 693 // discard the implication since it is always satisfied now. | 734 // discard the implication since it is always satisfied now. |
| 694 chunk.param.implies.removeWhere(splitParams.contains); | 735 chunk.param.implies.removeWhere(splitParams.contains); |
| 695 } | 736 } |
| 696 } | 737 } |
| 697 } | 738 } |
| 698 } | 739 } |
| OLD | NEW |