Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(84)

Side by Side Diff: lib/src/line_writer.dart

Issue 822273004: Add API to provide a selection range, and return the updated selection after formatting. (Closed) Base URL: https://github.com/dart-lang/dart_style.git@master
Patch Set: Remove redundant argument check. Created 5 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « lib/src/line_splitter.dart ('k') | lib/src/source_visitor.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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 }
OLDNEW
« no previous file with comments | « lib/src/line_splitter.dart ('k') | lib/src/source_visitor.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698