Chromium Code Reviews

Side by Side Diff: dart_style/lib/src/line_writer.dart

Issue 1400473008: Roll Observatory packages and add a roll script (Closed) Base URL: git@github.com:dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff |
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « dart_style/lib/src/line_splitting/solve_state_queue.dart ('k') | dart_style/lib/src/nesting_builder.dart » ('j') | no next file with comments »

Powered by Google App Engine