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 |