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