Index: dart_style/lib/src/chunk_builder.dart |
diff --git a/dart_style/lib/src/chunk_builder.dart b/dart_style/lib/src/chunk_builder.dart |
deleted file mode 100644 |
index c7ff91bb200efe5ed2b961456910e6b34da1e193..0000000000000000000000000000000000000000 |
--- a/dart_style/lib/src/chunk_builder.dart |
+++ /dev/null |
@@ -1,796 +0,0 @@ |
-// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
-// for details. All rights reserved. Use of this source code is governed by a |
-// BSD-style license that can be found in the LICENSE file. |
- |
-library dart_style.src.chunk_builder; |
- |
-import 'chunk.dart'; |
-import 'dart_formatter.dart'; |
-import 'debug.dart' as debug; |
-import 'line_writer.dart'; |
-import 'nesting_level.dart'; |
-import 'nesting_builder.dart'; |
-import 'rule/rule.dart'; |
-import 'source_code.dart'; |
-import 'whitespace.dart'; |
- |
-/// Takes the incremental serialized output of [SourceVisitor]--the source text |
-/// along with any comments and preserved whitespace--and produces a coherent |
-/// tree of [Chunk]s which can then be split into physical lines. |
-/// |
-/// Keeps track of leading indentation, expression nesting, and all of the hairy |
-/// code required to seamlessly integrate existing comments into the pure |
-/// output produced by [SourceVisitor]. |
-class ChunkBuilder { |
- final DartFormatter _formatter; |
- |
- /// The builder for the code surrounding the block that this writer is for, or |
- /// `null` if this is writing the top-level code. |
- final ChunkBuilder _parent; |
- |
- final SourceCode _source; |
- |
- final List<Chunk> _chunks; |
- |
- /// The whitespace that should be written to [_chunks] before the next |
- /// non-whitespace token. |
- /// |
- /// This ensures that changes to indentation and nesting also apply to the |
- /// most recent split, even if the visitor "creates" the split before changing |
- /// indentation or nesting. |
- Whitespace _pendingWhitespace = Whitespace.none; |
- |
- /// The nested stack of rules that are currently in use. |
- /// |
- /// New chunks are implicitly split by the innermost rule when the chunk is |
- /// ended. |
- final _rules = <Rule>[]; |
- |
- /// The set of rules known to contain hard splits that will in turn force |
- /// these rules to harden. |
- /// |
- /// This is accumulated lazily while chunks are being built. Then, once they |
- /// are all done, the rules are all hardened. We do this later because some |
- /// rules may not have all of their constraints fully wired up until after |
- /// the hard split appears. For example, a hard split in a positional |
- /// argument list needs to force the named arguments to split too, but we |
- /// don't create that rule until after the positional arguments are done. |
- final _hardSplitRules = new Set<Rule>(); |
- |
- /// The list of rules that are waiting until the next whitespace has been |
- /// written before they start. |
- final _lazyRules = <Rule>[]; |
- |
- /// The indexes of the chunks owned by each rule (except for hard splits). |
- final _ruleChunks = <Rule, List<int>>{}; |
- |
- /// The nested stack of spans that are currently being written. |
- final _openSpans = <OpenSpan>[]; |
- |
- /// The current state. |
- final _nesting = new NestingBuilder(); |
- |
- /// The stack of nesting levels where block arguments may start. |
- /// |
- /// A block argument's contents will nest at the last level in this stack. |
- final _blockArgumentNesting = <NestingLevel>[]; |
- |
- /// The index of the "current" chunk being written. |
- /// |
- /// If the last chunk is still being appended to, this is its index. |
- /// Otherwise, it is the index of the next chunk which will be created. |
- int get _currentChunkIndex { |
- if (_chunks.isEmpty) return 0; |
- if (_chunks.last.canAddText) return _chunks.length - 1; |
- return _chunks.length; |
- } |
- |
- /// Whether or not there was a leading comment that was flush left before any |
- /// other content was written. |
- /// |
- /// This is used when writing child blocks to make the parent chunk have the |
- /// right flush left value when a comment appears immediately inside the |
- /// block. |
- bool _firstFlushLeft = false; |
- |
- /// Whether there is pending whitespace that depends on the number of |
- /// newlines in the source. |
- /// |
- /// This is used to avoid calculating the newlines between tokens unless |
- /// actually needed since doing so is slow when done between every single |
- /// token pair. |
- bool get needsToPreserveNewlines => |
- _pendingWhitespace == Whitespace.oneOrTwoNewlines || |
- _pendingWhitespace == Whitespace.spaceOrNewline || |
- _pendingWhitespace == Whitespace.splitOrNewline; |
- |
- /// The number of characters of code that can fit in a single line. |
- int get pageWidth => _formatter.pageWidth; |
- |
- /// The current innermost rule. |
- Rule get rule => _rules.last; |
- |
- ChunkBuilder(this._formatter, this._source) |
- : _parent = null, |
- _chunks = [] { |
- indent(_formatter.indent); |
- startBlockArgumentNesting(); |
- } |
- |
- ChunkBuilder._(this._parent, this._formatter, this._source, this._chunks) { |
- startBlockArgumentNesting(); |
- } |
- |
- /// Writes [string], the text for a single token, to the output. |
- /// |
- /// By default, this also implicitly adds one level of nesting if we aren't |
- /// currently nested at all. We do this here so that if a comment appears |
- /// after any token within a statement or top-level form and that comment |
- /// leads to splitting, we correctly nest. Even pathological cases like: |
- /// |
- /// |
- /// import // comment |
- /// "this_gets_nested.dart"; |
- /// |
- /// If we didn't do this here, we'd have to call [nestExpression] after the |
- /// first token of practically every grammar production. |
- void write(String string) { |
- _emitPendingWhitespace(); |
- _writeText(string); |
- |
- _lazyRules.forEach(startRule); |
- _lazyRules.clear(); |
- |
- _nesting.commitNesting(); |
- } |
- |
- /// Writes a [WhitespaceChunk] of [type]. |
- void writeWhitespace(Whitespace type) { |
- _pendingWhitespace = type; |
- } |
- |
- /// Write a split owned by the current innermost rule. |
- /// |
- /// If [nesting] is given, uses that. Otherwise, uses the current nesting |
- /// level. If unsplit, it expands to a space if [space] is `true`. |
- /// |
- /// If [flushLeft] is `true`, then forces the next line to start at column |
- /// one regardless of any indentation or nesting. |
- /// |
- /// If [isDouble] is passed, forces the split to either be a single or double |
- /// newline. Otherwise, leaves it indeterminate. |
- Chunk split({bool space, bool isDouble, bool flushLeft}) => |
- _writeSplit(_rules.last, null, |
- flushLeft: flushLeft, isDouble: isDouble, spaceWhenUnsplit: space); |
- |
- /// Write a split owned by the current innermost rule. |
- /// |
- /// Unlike [split()], this ignores any current expression nesting. It always |
- /// indents the next line at the statement level. |
- Chunk blockSplit({bool space, bool isDouble}) => |
- _writeSplit(_rules.last, _nesting.blockNesting, |
- isDouble: isDouble, spaceWhenUnsplit: space); |
- |
- /// Outputs the series of [comments] and associated whitespace that appear |
- /// before [token] (which is not written by this). |
- /// |
- /// The list contains each comment as it appeared in the source between the |
- /// last token written and the next one that's about to be written. |
- /// |
- /// [linesBeforeToken] is the number of lines between the last comment (or |
- /// previous token if there are no comments) and the next token. |
- void writeComments( |
- List<SourceComment> comments, int linesBeforeToken, String token) { |
- // Corner case: if we require a blank line, but there exists one between |
- // some of the comments, or after the last one, then we don't need to |
- // enforce one before the first comment. Example: |
- // |
- // library foo; |
- // // comment |
- // |
- // class Bar {} |
- // |
- // Normally, a blank line is required after `library`, but since there is |
- // one after the comment, we don't need one before it. This is mainly so |
- // that commented out directives stick with their preceding group. |
- if (_pendingWhitespace == Whitespace.twoNewlines && |
- comments.first.linesBefore < 2) { |
- if (linesBeforeToken > 1) { |
- _pendingWhitespace = Whitespace.newline; |
- } else { |
- for (var i = 1; i < comments.length; i++) { |
- if (comments[i].linesBefore > 1) { |
- _pendingWhitespace = Whitespace.newline; |
- break; |
- } |
- } |
- } |
- } |
- |
- // Corner case: if the comments are completely inline (i.e. just a series |
- // of block comments with no newlines before, after, or between them), then |
- // they will eat any pending newlines. Make sure that doesn't happen by |
- // putting the pending whitespace before the first comment and moving them |
- // to their own line. Turns this: |
- // |
- // library foo; /* a */ /* b */ import 'a.dart'; |
- // |
- // into: |
- // |
- // library foo; |
- // |
- // /* a */ /* b */ |
- // import 'a.dart'; |
- if (linesBeforeToken == 0 && |
- comments.every((comment) => comment.isInline)) { |
- if (_pendingWhitespace.minimumLines > 0) { |
- comments.first.linesBefore = _pendingWhitespace.minimumLines; |
- linesBeforeToken = 1; |
- } |
- } |
- |
- // Write each comment and the whitespace between them. |
- for (var i = 0; i < comments.length; i++) { |
- var comment = comments[i]; |
- |
- preserveNewlines(comment.linesBefore); |
- |
- // Don't emit a space because we'll handle it below. If we emit it here, |
- // we may get a trailing space if the comment needs a line before it. |
- if (_pendingWhitespace == Whitespace.space) { |
- _pendingWhitespace = Whitespace.none; |
- } |
- _emitPendingWhitespace(); |
- |
- if (comment.linesBefore == 0) { |
- // If we're sitting on a split, move the comment before it to adhere it |
- // to the preceding text. |
- if (_shouldMoveCommentBeforeSplit(comment.text)) { |
- _chunks.last.allowText(); |
- } |
- |
- // The comment follows other text, so we need to decide if it gets a |
- // space before it or not. |
- if (_needsSpaceBeforeComment(isLineComment: comment.isLineComment)) { |
- _writeText(" "); |
- } |
- } else { |
- // The comment starts a line, so make sure it stays on its own line. |
- _writeHardSplit( |
- nest: true, |
- flushLeft: comment.flushLeft, |
- double: comment.linesBefore > 1); |
- } |
- |
- _writeText(comment.text); |
- |
- if (comment.selectionStart != null) { |
- startSelectionFromEnd(comment.text.length - comment.selectionStart); |
- } |
- |
- if (comment.selectionEnd != null) { |
- endSelectionFromEnd(comment.text.length - comment.selectionEnd); |
- } |
- |
- // Make sure there is at least one newline after a line comment and allow |
- // one or two after a block comment that has nothing after it. |
- var linesAfter; |
- if (i < comments.length - 1) { |
- linesAfter = comments[i + 1].linesBefore; |
- } else { |
- linesAfter = linesBeforeToken; |
- |
- // Always force a newline after multi-line block comments. Prevents |
- // mistakes like: |
- // |
- // /** |
- // * Some doc comment. |
- // */ someFunction() { ... } |
- if (linesAfter == 0 && comments.last.text.contains("\n")) { |
- linesAfter = 1; |
- } |
- } |
- |
- if (linesAfter > 0) _writeHardSplit(nest: true, double: linesAfter > 1); |
- } |
- |
- // If the comment has text following it (aside from a grouping character), |
- // it needs a trailing space. |
- if (_needsSpaceAfterLastComment(comments, token)) { |
- _pendingWhitespace = Whitespace.space; |
- } |
- |
- preserveNewlines(linesBeforeToken); |
- } |
- |
- /// If the current pending whitespace allows some source discretion, pins |
- /// that down given that the source contains [numLines] newlines at that |
- /// point. |
- void preserveNewlines(int numLines) { |
- // If we didn't know how many newlines the user authored between the last |
- // token and this one, now we do. |
- switch (_pendingWhitespace) { |
- case Whitespace.spaceOrNewline: |
- if (numLines > 0) { |
- _pendingWhitespace = Whitespace.nestedNewline; |
- } else { |
- _pendingWhitespace = Whitespace.space; |
- } |
- break; |
- |
- case Whitespace.splitOrNewline: |
- if (numLines > 0) { |
- _pendingWhitespace = Whitespace.nestedNewline; |
- } else { |
- _pendingWhitespace = Whitespace.none; |
- split(space: true); |
- } |
- break; |
- |
- case Whitespace.oneOrTwoNewlines: |
- if (numLines > 1) { |
- _pendingWhitespace = Whitespace.twoNewlines; |
- } else { |
- _pendingWhitespace = Whitespace.newline; |
- } |
- break; |
- } |
- } |
- |
- /// Creates a new indentation level [spaces] deeper than the current one. |
- /// |
- /// If omitted, [spaces] defaults to [Indent.block]. |
- void indent([int spaces]) { |
- _nesting.indent(spaces); |
- } |
- |
- /// Discards the most recent indentation level. |
- void unindent() { |
- _nesting.unindent(); |
- } |
- |
- /// Starts a new span with [cost]. |
- /// |
- /// Each call to this needs a later matching call to [endSpan]. |
- void startSpan([int cost = Cost.normal]) { |
- _openSpans.add(new OpenSpan(_currentChunkIndex, cost)); |
- } |
- |
- /// Ends the innermost span. |
- void endSpan() { |
- var openSpan = _openSpans.removeLast(); |
- |
- // A span that just covers a single chunk can't be split anyway. |
- var end = _currentChunkIndex; |
- if (openSpan.start == end) return; |
- |
- // Add the span to every chunk that can split it. |
- var span = new Span(openSpan.cost); |
- for (var i = openSpan.start; i < end; i++) { |
- var chunk = _chunks[i]; |
- if (!chunk.isHardSplit) chunk.spans.add(span); |
- } |
- } |
- |
- /// Starts a new [Rule]. |
- /// |
- /// If omitted, defaults to a new [SimpleRule]. |
- void startRule([Rule rule]) { |
- if (rule == null) rule = new SimpleRule(); |
- |
- // See if any of the rules that contain this one care if it splits. |
- _rules.forEach((outer) => outer.contain(rule)); |
- _rules.add(rule); |
- } |
- |
- /// Starts a new [Rule] that comes into play *after* the next whitespace |
- /// (including comments) is written. |
- /// |
- /// This is used for binary operators who want to start a rule before the |
- /// first operand but not get forced to split if a comment appears before the |
- /// entire expression. |
- /// |
- /// If [rule] is omitted, defaults to a new [SimpleRule]. |
- void startLazyRule([Rule rule]) { |
- if (rule == null) rule = new SimpleRule(); |
- |
- _lazyRules.add(rule); |
- } |
- |
- /// Ends the innermost rule. |
- void endRule() { |
- _rules.removeLast(); |
- } |
- |
- /// Pre-emptively forces all of the current rules to become hard splits. |
- /// |
- /// This is called by [SourceVisitor] when it can determine that a rule will |
- /// will always be split. Turning it (and the surrounding rules) into hard |
- /// splits lets the writer break the output into smaller pieces for the line |
- /// splitter, which helps performance and avoids failing on very large input. |
- /// |
- /// In particular, it's easy for the visitor to know that collections with a |
- /// large number of items must split. Doing that early avoids crashing the |
- /// splitter when it tries to recurse on huge collection literals. |
- void forceRules() => _handleHardSplit(); |
- |
- /// Begins a new expression nesting level [indent] spaces deeper than the |
- /// current one if it splits. |
- /// |
- /// If [indent] is omitted, defaults to [Indent.expression]. If [now] is |
- /// `true`, commits the nesting change immediately instead of waiting until |
- /// after the next chunk of text is written. |
- void nestExpression({int indent, bool now}) { |
- if (now == null) now = false; |
- |
- _nesting.nest(indent); |
- if (now) _nesting.commitNesting(); |
- } |
- |
- /// Discards the most recent level of expression nesting. |
- /// |
- /// Expressions that are more nested will get increased indentation when split |
- /// if the previous line has a lower level of nesting. |
- void unnest() { |
- _nesting.unnest(); |
- } |
- |
- /// Marks the selection starting point as occurring [fromEnd] characters to |
- /// the left of the end of what's currently been written. |
- /// |
- /// It counts backwards from the end because this is called *after* the chunk |
- /// of text containing the selection has been output. |
- void startSelectionFromEnd(int fromEnd) { |
- assert(_chunks.isNotEmpty); |
- _chunks.last.startSelectionFromEnd(fromEnd); |
- } |
- |
- /// Marks the selection ending point as occurring [fromEnd] characters to the |
- /// left of the end of what's currently been written. |
- /// |
- /// It counts backwards from the end because this is called *after* the chunk |
- /// of text containing the selection has been output. |
- void endSelectionFromEnd(int fromEnd) { |
- assert(_chunks.isNotEmpty); |
- _chunks.last.endSelectionFromEnd(fromEnd); |
- } |
- |
- /// Captures the current nesting level as marking where subsequent block |
- /// arguments should start. |
- void startBlockArgumentNesting() { |
- _blockArgumentNesting.add(_nesting.currentNesting); |
- } |
- |
- /// Releases the last nesting level captured by [startBlockArgumentNesting]. |
- void endBlockArgumentNesting() { |
- _blockArgumentNesting.removeLast(); |
- } |
- |
- /// Starts a new block as a child of the current chunk. |
- /// |
- /// Nested blocks are handled using their own independent [LineWriter]. |
- ChunkBuilder startBlock() { |
- var builder = |
- new ChunkBuilder._(this, _formatter, _source, _chunks.last.blockChunks); |
- |
- // A block always starts off indented one level. |
- builder.indent(); |
- |
- return builder; |
- } |
- |
- /// Ends this [ChunkBuilder], which must have been created by [startBlock()]. |
- /// |
- /// Forces the chunk that owns the block to split if it can tell that the |
- /// block contents will always split. It does that by looking for hard splits |
- /// in the block. If [ignoredSplit] is given, that rule will be ignored |
- /// when determining if a block contains a hard split. If [forceSplit] is |
- /// `true`, the block is considered to always split. |
- /// |
- /// Returns the previous writer for the surrounding block. |
- ChunkBuilder endBlock(HardSplitRule ignoredSplit, {bool forceSplit}) { |
- _divideChunks(); |
- |
- // If we don't already know if the block is going to split, see if it |
- // contains any hard splits or is longer than a page. |
- if (!forceSplit) { |
- var length = 0; |
- for (var chunk in _chunks) { |
- length += chunk.length + chunk.unsplitBlockLength; |
- if (length > _formatter.pageWidth) { |
- forceSplit = true; |
- break; |
- } |
- |
- if (chunk.isHardSplit && chunk.rule != ignoredSplit) { |
- forceSplit = true; |
- break; |
- } |
- } |
- } |
- |
- // If there is a hard newline within the block, force the surrounding rule |
- // for it so that we apply that constraint. |
- if (forceSplit) _parent.forceRules(); |
- |
- // Write the split for the block contents themselves. |
- _parent._writeSplit(_parent._rules.last, _parent._blockArgumentNesting.last, |
- flushLeft: _firstFlushLeft); |
- return _parent; |
- } |
- |
- /// Finishes writing and returns a [SourceCode] containing the final output |
- /// and updated selection, if any. |
- SourceCode end() { |
- _writeHardSplit(); |
- _divideChunks(); |
- |
- if (debug.traceChunkBuilder) { |
- debug.log(debug.green("\nBuilt:")); |
- debug.dumpChunks(0, _chunks); |
- debug.log(); |
- } |
- |
- var writer = new LineWriter(_formatter, _chunks); |
- var result = writer.writeLines(_formatter.indent, |
- isCompilationUnit: _source.isCompilationUnit); |
- |
- var selectionStart; |
- var selectionLength; |
- if (_source.selectionStart != null) { |
- selectionStart = result.selectionStart; |
- var selectionEnd = result.selectionEnd; |
- |
- // If we haven't hit the beginning and/or end of the selection yet, they |
- // must be at the very end of the code. |
- if (selectionStart == null) selectionStart = writer.length; |
- if (selectionEnd == null) selectionEnd = writer.length; |
- |
- selectionLength = selectionEnd - selectionStart; |
- } |
- |
- return new SourceCode(result.text, |
- uri: _source.uri, |
- isCompilationUnit: _source.isCompilationUnit, |
- selectionStart: selectionStart, |
- selectionLength: selectionLength); |
- } |
- |
- /// Writes the current pending [Whitespace] to the output, if any. |
- /// |
- /// This should only be called after source lines have been preserved to turn |
- /// any ambiguous whitespace into a concrete choice. |
- void _emitPendingWhitespace() { |
- // Output any pending whitespace first now that we know it won't be |
- // trailing. |
- switch (_pendingWhitespace) { |
- case Whitespace.space: |
- _writeText(" "); |
- break; |
- |
- case Whitespace.newline: |
- _writeHardSplit(); |
- break; |
- |
- case Whitespace.nestedNewline: |
- _writeHardSplit(nest: true); |
- break; |
- |
- case Whitespace.newlineFlushLeft: |
- _writeHardSplit(nest: true, flushLeft: true); |
- break; |
- |
- case Whitespace.twoNewlines: |
- _writeHardSplit(double: true); |
- break; |
- |
- case Whitespace.spaceOrNewline: |
- case Whitespace.splitOrNewline: |
- case Whitespace.oneOrTwoNewlines: |
- // We should have pinned these down before getting here. |
- assert(false); |
- break; |
- } |
- |
- _pendingWhitespace = Whitespace.none; |
- } |
- |
- /// Returns `true` if the last chunk is a split that should be moved after the |
- /// comment that is about to be written. |
- bool _shouldMoveCommentBeforeSplit(String comment) { |
- // Not if there is nothing before it. |
- if (_chunks.isEmpty) return false; |
- |
- // Multi-line comments are always pushed to the next line. |
- if (comment.contains("\n")) return false; |
- |
- // If the text before the split is an open grouping character, we don't |
- // want to adhere the comment to that. |
- var text = _chunks.last.text; |
- return !text.endsWith("(") && !text.endsWith("[") && !text.endsWith("{"); |
- } |
- |
- /// Returns `true` if a space should be output between the end of the current |
- /// output and the subsequent comment which is about to be written. |
- /// |
- /// This is only called if the comment is trailing text in the unformatted |
- /// source. In most cases, a space will be output to separate the comment |
- /// from what precedes it. This returns false if: |
- /// |
- /// * This comment does begin the line in the output even if it didn't in |
- /// the source. |
- /// * The comment is a block comment immediately following a grouping |
- /// character (`(`, `[`, or `{`). This is to allow `foo(/* comment */)`, |
- /// et. al. |
- bool _needsSpaceBeforeComment({bool isLineComment}) { |
- // Not at the start of the file. |
- if (_chunks.isEmpty) return false; |
- |
- // Not at the start of a line. |
- if (!_chunks.last.canAddText) return false; |
- |
- var text = _chunks.last.text; |
- if (text.endsWith("\n")) return false; |
- |
- // Always put a space before line comments. |
- if (isLineComment) return true; |
- |
- // Block comments do not get a space if following a grouping character. |
- return !text.endsWith("(") && !text.endsWith("[") && !text.endsWith("{"); |
- } |
- |
- /// Returns `true` if a space should be output after the last comment which |
- /// was just written and the token that will be written. |
- bool _needsSpaceAfterLastComment(List<SourceComment> comments, String token) { |
- // Not if there are no comments. |
- if (comments.isEmpty) return false; |
- |
- // Not at the beginning of a line. |
- if (!_chunks.last.canAddText) return false; |
- |
- // Otherwise, it gets a space if the following token is not a delimiter or |
- // the empty string, for EOF. |
- return token != ")" && |
- token != "]" && |
- token != "}" && |
- token != "," && |
- token != ";" && |
- token != ""; |
- } |
- |
- /// Appends a hard split with the current indentation and nesting (the latter |
- /// only if [nest] is `true`). |
- /// |
- /// If [double] is `true` or `false`, forces a since or double line to be |
- /// output. Otherwise, it is left indeterminate. |
- /// |
- /// If [flushLeft] is `true`, then the split will always cause the next line |
- /// to be at column zero. Otherwise, it uses the normal indentation and |
- /// nesting behavior. |
- void _writeHardSplit({bool nest: false, bool double, bool flushLeft}) { |
- // A hard split overrides any other whitespace. |
- _pendingWhitespace = null; |
- _writeSplit(new HardSplitRule(), nest ? null : _nesting.blockNesting, |
- flushLeft: flushLeft, isDouble: double); |
- } |
- |
- /// Ends the current chunk (if any) with the given split information. |
- /// |
- /// Returns the chunk. |
- Chunk _writeSplit(Rule rule, NestingLevel nesting, |
- {bool flushLeft, bool isDouble, bool spaceWhenUnsplit}) { |
- if (_chunks.isEmpty) { |
- if (flushLeft != null) _firstFlushLeft = flushLeft; |
- |
- return null; |
- } |
- |
- if (nesting == null) nesting = _nesting.nesting; |
- |
- var chunk = _chunks.last; |
- chunk.applySplit(rule, _nesting.indentation, nesting, |
- flushLeft: flushLeft, |
- isDouble: isDouble, |
- spaceWhenUnsplit: spaceWhenUnsplit); |
- |
- // Keep track of which chunks are owned by the rule. |
- if (rule is! HardSplitRule) { |
- _ruleChunks.putIfAbsent(rule, () => []).add(_chunks.length - 1); |
- } |
- |
- if (chunk.isHardSplit) _handleHardSplit(); |
- |
- return chunk; |
- } |
- |
- /// Writes [text] to either the current chunk or a new one if the current |
- /// chunk is complete. |
- void _writeText(String text) { |
- if (_chunks.isNotEmpty && _chunks.last.canAddText) { |
- _chunks.last.appendText(text); |
- } else { |
- _chunks.add(new Chunk(text)); |
- } |
- } |
- |
- /// Returns true if we can divide the chunks at [index] and line split the |
- /// ones before and after that separately. |
- bool _canDivideAt(int i) { |
- var chunk = _chunks[i]; |
- if (!chunk.isHardSplit) return false; |
- if (chunk.nesting.isNested) return false; |
- if (chunk.blockChunks.isNotEmpty) return false; |
- |
- // Make sure we don't split the line in the middle of a rule. |
- var chunks = _ruleChunks[chunk.rule]; |
- if (chunks != null && chunks.any((other) => other > i)) return false; |
- |
- return true; |
- } |
- |
- /// Pre-processes the chunks after they are done being written by the visitor |
- /// but before they are run through the line splitter. |
- /// |
- /// Marks ranges of chunks that can be line split independently to keep the |
- /// batches we send to [LineSplitter] small. |
- void _divideChunks() { |
- // Harden all of the rules that we know get forced by containing hard |
- // splits, along with all of the other rules they constrain. |
- _hardenRules(); |
- |
- // Now that we know where all of the divided chunk sections are, mark the |
- // chunks. |
- for (var i = 0; i < _chunks.length; i++) { |
- _chunks[i].markDivide(_canDivideAt(i)); |
- } |
- } |
- |
- /// Hardens the active rules when a hard split occurs within them. |
- void _handleHardSplit() { |
- if (_rules.isEmpty) return; |
- |
- // If the current rule doesn't care, it will "eat" the hard split and no |
- // others will care either. |
- if (!_rules.last.splitsOnInnerRules) return; |
- |
- // Start with the innermost rule. This will traverse the other rules it |
- // constrains. |
- _hardSplitRules.add(_rules.last); |
- } |
- |
- /// Replaces all of the previously hardened rules with hard splits, along |
- /// with every rule that those constrain to also split. |
- /// |
- /// This should only be called after all chunks have been written. |
- void _hardenRules() { |
- if (_hardSplitRules.isEmpty) return; |
- |
- // Harden all of the rules that are constrained by [rules] as well. |
- var hardenedRules = new Set(); |
- walkConstraints(rule) { |
- if (hardenedRules.contains(rule)) return; |
- hardenedRules.add(rule); |
- |
- // Follow this rule's constraints, recursively. |
- for (var other in _ruleChunks.keys) { |
- if (other == rule) continue; |
- |
- if (rule.constrain(rule.fullySplitValue, other) == |
- other.fullySplitValue) { |
- walkConstraints(other); |
- } |
- } |
- } |
- |
- for (var rule in _hardSplitRules) { |
- walkConstraints(rule); |
- } |
- |
- // Harden every chunk that uses one of these rules. |
- for (var chunk in _chunks) { |
- if (hardenedRules.contains(chunk.rule)) { |
- chunk.harden(); |
- } |
- } |
- } |
-} |