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 |