| 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 |