| OLD | NEW | 
 | (Empty) | 
|    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 |  | 
|    3 // BSD-style license that can be found in the LICENSE file. |  | 
|    4  |  | 
|    5 library dart_style.src.line_writer; |  | 
|    6  |  | 
|    7 import 'chunk.dart'; |  | 
|    8 import 'dart_formatter.dart'; |  | 
|    9 import 'debug.dart' as debug; |  | 
|   10 import 'line_splitting/line_splitter.dart'; |  | 
|   11 import 'whitespace.dart'; |  | 
|   12  |  | 
|   13 /// Given a series of chunks, splits them into lines and writes the result to |  | 
|   14 /// a buffer. |  | 
|   15 class LineWriter { |  | 
|   16   final _buffer = new StringBuffer(); |  | 
|   17  |  | 
|   18   final List<Chunk> _chunks; |  | 
|   19  |  | 
|   20   final String _lineEnding; |  | 
|   21  |  | 
|   22   /// The number of characters allowed in a single line. |  | 
|   23   final int pageWidth; |  | 
|   24  |  | 
|   25   /// The number of characters of additional indentation to apply to each line. |  | 
|   26   /// |  | 
|   27   /// This is used when formatting blocks to get the output into the right |  | 
|   28   /// column based on where the block appears. |  | 
|   29   final int _blockIndentation; |  | 
|   30  |  | 
|   31   /// The cache of blocks that have already been formatted. |  | 
|   32   final Map<_BlockKey, FormatResult> _blockCache; |  | 
|   33  |  | 
|   34   /// The offset in [_buffer] where the selection starts in the formatted code. |  | 
|   35   /// |  | 
|   36   /// This will be `null` if there is no selection or the writer hasn't reached |  | 
|   37   /// the beginning of the selection yet. |  | 
|   38   int _selectionStart; |  | 
|   39  |  | 
|   40   /// The offset in [_buffer] where the selection ends in the formatted code. |  | 
|   41   /// |  | 
|   42   /// This will be `null` if there is no selection or the writer hasn't reached |  | 
|   43   /// the end of the selection yet. |  | 
|   44   int _selectionEnd; |  | 
|   45  |  | 
|   46   /// The number of characters that have been written to the output. |  | 
|   47   int get length => _buffer.length; |  | 
|   48  |  | 
|   49   LineWriter(DartFormatter formatter, this._chunks) |  | 
|   50       : _lineEnding = formatter.lineEnding, |  | 
|   51         pageWidth = formatter.pageWidth, |  | 
|   52         _blockIndentation = 0, |  | 
|   53         _blockCache = {}; |  | 
|   54  |  | 
|   55   /// Creates a line writer for a block. |  | 
|   56   LineWriter._(this._chunks, this._lineEnding, this.pageWidth, |  | 
|   57       this._blockIndentation, this._blockCache) { |  | 
|   58     // There is always a newline after the opening delimiter. |  | 
|   59     _buffer.write(_lineEnding); |  | 
|   60   } |  | 
|   61  |  | 
|   62   /// Gets the results of formatting the child block of [chunk] at with |  | 
|   63   /// starting [column]. |  | 
|   64   /// |  | 
|   65   /// If that block has already been formatted, reuses the results. |  | 
|   66   /// |  | 
|   67   /// The column is the column for the delimiters. The contents of the block |  | 
|   68   /// are always implicitly one level deeper than that. |  | 
|   69   /// |  | 
|   70   ///     main() { |  | 
|   71   ///       function(() { |  | 
|   72   ///         block; |  | 
|   73   ///       }); |  | 
|   74   ///     } |  | 
|   75   /// |  | 
|   76   /// When we format the anonymous lambda, [column] will be 2, not 4. |  | 
|   77   FormatResult formatBlock(Chunk chunk, int column) { |  | 
|   78     var key = new _BlockKey(chunk, column); |  | 
|   79  |  | 
|   80     // Use the cached one if we have it. |  | 
|   81     var cached = _blockCache[key]; |  | 
|   82     if (cached != null) return cached; |  | 
|   83  |  | 
|   84     var writer = new LineWriter._( |  | 
|   85         chunk.blockChunks, _lineEnding, pageWidth, column, _blockCache); |  | 
|   86  |  | 
|   87     // TODO(rnystrom): Passing in an initial indent here is hacky. The |  | 
|   88     // LineWriter ensures all but the first chunk have a block indent, and this |  | 
|   89     // handles the first chunk. Do something cleaner. |  | 
|   90     var result = writer.writeLines(Indent.block, flushLeft: chunk.flushLeft); |  | 
|   91     return _blockCache[key] = result; |  | 
|   92   } |  | 
|   93  |  | 
|   94   /// Takes all of the chunks and divides them into sublists and line splits |  | 
|   95   /// each list. |  | 
|   96   /// |  | 
|   97   /// Since this is linear and line splitting is worse it's good to feed the |  | 
|   98   /// line splitter smaller lists of chunks when possible. |  | 
|   99   FormatResult writeLines(int firstLineIndent, |  | 
|  100       {bool isCompilationUnit: false, bool flushLeft: false}) { |  | 
|  101     // Now that we know what hard splits there will be, break the chunks into |  | 
|  102     // independently splittable lines. |  | 
|  103     var newlines = 0; |  | 
|  104     var indent = firstLineIndent; |  | 
|  105     var totalCost = 0; |  | 
|  106     var start = 0; |  | 
|  107  |  | 
|  108     for (var i = 0; i < _chunks.length; i++) { |  | 
|  109       var chunk = _chunks[i]; |  | 
|  110       if (!chunk.canDivide) continue; |  | 
|  111  |  | 
|  112       totalCost += |  | 
|  113           _completeLine(newlines, indent, start, i + 1, flushLeft: flushLeft); |  | 
|  114  |  | 
|  115       // Get ready for the next line. |  | 
|  116       newlines = chunk.isDouble ? 2 : 1; |  | 
|  117       indent = chunk.indent; |  | 
|  118       flushLeft = chunk.flushLeft; |  | 
|  119       start = i + 1; |  | 
|  120     } |  | 
|  121  |  | 
|  122     if (start < _chunks.length) { |  | 
|  123       totalCost += _completeLine(newlines, indent, start, _chunks.length, |  | 
|  124           flushLeft: flushLeft); |  | 
|  125     } |  | 
|  126  |  | 
|  127     // Be a good citizen, end with a newline. |  | 
|  128     if (isCompilationUnit) _buffer.write(_lineEnding); |  | 
|  129  |  | 
|  130     return new FormatResult( |  | 
|  131         _buffer.toString(), totalCost, _selectionStart, _selectionEnd); |  | 
|  132   } |  | 
|  133  |  | 
|  134   /// Takes the first [length] of the chunks with leading [indent], removes |  | 
|  135   /// them, and runs the [LineSplitter] on them. |  | 
|  136   int _completeLine(int newlines, int indent, int start, int end, |  | 
|  137       {bool flushLeft}) { |  | 
|  138     // Write the newlines required by the previous line. |  | 
|  139     for (var j = 0; j < newlines; j++) { |  | 
|  140       _buffer.write(_lineEnding); |  | 
|  141     } |  | 
|  142  |  | 
|  143     var chunks = _chunks.sublist(start, end); |  | 
|  144  |  | 
|  145     if (debug.traceLineWriter) { |  | 
|  146       debug.log(debug.green("\nWriting:")); |  | 
|  147       debug.dumpChunks(start, chunks); |  | 
|  148       debug.log(); |  | 
|  149     } |  | 
|  150  |  | 
|  151     // Run the line splitter. |  | 
|  152     var splitter = new LineSplitter(this, chunks, _blockIndentation, indent, |  | 
|  153         flushLeft: flushLeft); |  | 
|  154     var splits = splitter.apply(); |  | 
|  155  |  | 
|  156     // Write the indentation of the first line. |  | 
|  157     if (!flushLeft) { |  | 
|  158       _buffer.write(" " * (indent + _blockIndentation)); |  | 
|  159     } |  | 
|  160  |  | 
|  161     // Write each chunk with the appropriate splits between them. |  | 
|  162     for (var i = 0; i < chunks.length; i++) { |  | 
|  163       var chunk = chunks[i]; |  | 
|  164       _writeChunk(chunk); |  | 
|  165  |  | 
|  166       if (chunk.blockChunks.isNotEmpty) { |  | 
|  167         if (!splits.shouldSplitAt(i)) { |  | 
|  168           // This block didn't split (which implies none of the child blocks |  | 
|  169           // of that block split either, recursively), so write them all inline. |  | 
|  170           _writeChunksUnsplit(chunk.blockChunks); |  | 
|  171         } else { |  | 
|  172           // Include the formatted block contents. |  | 
|  173           var block = formatBlock(chunk, splits.getColumn(i)); |  | 
|  174  |  | 
|  175           // If this block contains one of the selection markers, tell the |  | 
|  176           // writer where it ended up in the final output. |  | 
|  177           if (block.selectionStart != null) { |  | 
|  178             _selectionStart = length + block.selectionStart; |  | 
|  179           } |  | 
|  180  |  | 
|  181           if (block.selectionEnd != null) { |  | 
|  182             _selectionEnd = length + block.selectionEnd; |  | 
|  183           } |  | 
|  184  |  | 
|  185           _buffer.write(block.text); |  | 
|  186         } |  | 
|  187       } |  | 
|  188  |  | 
|  189       if (i == chunks.length - 1) { |  | 
|  190         // Don't write trailing whitespace after the last chunk. |  | 
|  191       } else if (splits.shouldSplitAt(i)) { |  | 
|  192         _buffer.write(_lineEnding); |  | 
|  193         if (chunk.isDouble) _buffer.write(_lineEnding); |  | 
|  194  |  | 
|  195         _buffer.write(" " * (splits.getColumn(i))); |  | 
|  196       } else { |  | 
|  197         if (chunk.spaceWhenUnsplit) _buffer.write(" "); |  | 
|  198       } |  | 
|  199     } |  | 
|  200  |  | 
|  201     return splits.cost; |  | 
|  202   } |  | 
|  203  |  | 
|  204   /// Writes [chunks] (and any child chunks of them, recursively) without any |  | 
|  205   /// splitting. |  | 
|  206   void _writeChunksUnsplit(List<Chunk> chunks) { |  | 
|  207     for (var chunk in chunks) { |  | 
|  208       _writeChunk(chunk); |  | 
|  209  |  | 
|  210       if (chunk.spaceWhenUnsplit) _buffer.write(" "); |  | 
|  211  |  | 
|  212       // Recurse into the block. |  | 
|  213       _writeChunksUnsplit(chunk.blockChunks); |  | 
|  214     } |  | 
|  215   } |  | 
|  216  |  | 
|  217   /// Writes [chunk] to the output and updates the selection if the chunk |  | 
|  218   /// contains a selection marker. |  | 
|  219   void _writeChunk(Chunk chunk) { |  | 
|  220     if (chunk.selectionStart != null) { |  | 
|  221       _selectionStart = length + chunk.selectionStart; |  | 
|  222     } |  | 
|  223  |  | 
|  224     if (chunk.selectionEnd != null) { |  | 
|  225       _selectionEnd = length + chunk.selectionEnd; |  | 
|  226     } |  | 
|  227  |  | 
|  228     _buffer.write(chunk.text); |  | 
|  229   } |  | 
|  230 } |  | 
|  231  |  | 
|  232 /// Key type for the formatted block cache. |  | 
|  233 /// |  | 
|  234 /// To cache formatted blocks, we just need to know which block it is (the |  | 
|  235 /// index of its parent chunk) and how far it was indented when we formatted it |  | 
|  236 /// (the starting column). |  | 
|  237 class _BlockKey { |  | 
|  238   /// The index of the chunk in the surrounding chunk list that contains this |  | 
|  239   /// block. |  | 
|  240   final Chunk chunk; |  | 
|  241  |  | 
|  242   /// The absolute zero-based column number where the block starts. |  | 
|  243   final int column; |  | 
|  244  |  | 
|  245   _BlockKey(this.chunk, this.column); |  | 
|  246  |  | 
|  247   bool operator ==(other) { |  | 
|  248     if (other is! _BlockKey) return false; |  | 
|  249     return chunk == other.chunk && column == other.column; |  | 
|  250   } |  | 
|  251  |  | 
|  252   int get hashCode => chunk.hashCode ^ column.hashCode; |  | 
|  253 } |  | 
|  254  |  | 
|  255 /// The result of formatting a series of chunks. |  | 
|  256 class FormatResult { |  | 
|  257   /// The resulting formatted text, including newlines and leading whitespace |  | 
|  258   /// to reach the proper column. |  | 
|  259   final String text; |  | 
|  260  |  | 
|  261   /// The numeric cost of the chosen solution. |  | 
|  262   final int cost; |  | 
|  263  |  | 
|  264   /// Where in the resulting buffer the selection starting point should appear |  | 
|  265   /// if it was contained within this split list of chunks. |  | 
|  266   /// |  | 
|  267   /// Otherwise, this is `null`. |  | 
|  268   final int selectionStart; |  | 
|  269  |  | 
|  270   /// Where in the resulting buffer the selection end point should appear if it |  | 
|  271   /// was contained within this split list of chunks. |  | 
|  272   /// |  | 
|  273   /// Otherwise, this is `null`. |  | 
|  274   final int selectionEnd; |  | 
|  275  |  | 
|  276   FormatResult(this.text, this.cost, this.selectionStart, this.selectionEnd); |  | 
|  277 } |  | 
| OLD | NEW |