Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(218)

Side by Side Diff: pkg/analyzer/lib/src/services/writer.dart

Issue 381663004: Improve wrapping during formatting. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Performance fix Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library source_writer; 5 library source_writer;
6 6
7 7 import 'dart:math' as math;
8 8
9 class Line { 9 class Line {
10 10
11 final List<LineToken> tokens = <LineToken>[]; 11 final List<LineToken> tokens = <LineToken>[];
12 final bool useTabs; 12 final bool useTabs;
13 final int spacesPerIndent; 13 final int spacesPerIndent;
14 final int indentLevel; 14 final int indentLevel;
15 final LinePrinter printer; 15 final LinePrinter printer;
16 16
17 Line({this.indentLevel: 0, this.useTabs: false, this.spacesPerIndent: 2, 17 Line({this.indentLevel: 0, this.useTabs: false, this.spacesPerIndent: 2,
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
77 SimpleLineBreaker(this.maxLength, [this.indenter]) { 77 SimpleLineBreaker(this.maxLength, [this.indenter]) {
78 if (indenter == null) { 78 if (indenter == null) {
79 indenter = NO_OP_INDENTER; 79 indenter = NO_OP_INDENTER;
80 } 80 }
81 } 81 }
82 82
83 String printLine(Line line) { 83 String printLine(Line line) {
84 var buf = new StringBuffer(); 84 var buf = new StringBuffer();
85 var chunks = breakLine(line); 85 var chunks = breakLine(line);
86 for (var i = 0; i < chunks.length; ++i) { 86 for (var i = 0; i < chunks.length; ++i) {
87 var chunk = chunks[i];
87 if (i > 0) { 88 if (i > 0) {
88 buf.write(indent(chunks[i], line.indentLevel)); 89 buf.write(indent(chunk, chunk.indent));
89 } else { 90 } else {
90 buf.write(chunks[i]); 91 buf.write(chunk);
91 } 92 }
92 } 93 }
93 return buf.toString(); 94 return buf.toString();
94 } 95 }
95 96
96 String indent(Chunk chunk, int level) => 97 String indent(Chunk chunk, int level) {
97 '\n' + indenter(level + 2) + chunk.toString(); 98 return '\n' + indenter(level) + chunk.toString();
99 }
98 100
99 List<Chunk> breakLine(Line line) { 101 List<Chunk> breakLine(Line line) {
100 102 List<LineToken> tokens = preprocess(line.tokens);
101 var tokens = preprocess(line.tokens); 103 List<Chunk> chunks = <Chunk>[new Chunk(line.indentLevel, maxLength, tokens)] ;
102 104 while (true) {
103 var chunks = <Chunk>[]; 105 List<Chunk> newChunks = <Chunk>[];
104 106 bool hasChanges = false;
105 // The current unbroken line 107 for (Chunk chunk in chunks) {
106 var current = new Chunk(maxLength: maxLength); 108 tokens = chunk.tokens;
107 109 if (chunk.length > maxLength) {
108 // A tentative working chunk that will either start a new line or get 110 if (chunk.hasAnySpace()) {
109 // absorbed into 'current' 111 int weight = chunk.findMinSpaceWeight();
110 var work = new Chunk(maxLength: maxLength); 112 int newIndent = chunk.indent;
111 113 if (weight == DEFAULT_SPACE_WEIGHT) {
112 tokens.forEach((tok) { 114 int start = 0;
113 115 int length = 0;
114 if (goodStart(tok, work)) { 116 for (int i = 0; i < tokens.length; i++) {
115 if (current.fits(work)) { 117 LineToken token = tokens[i];
116 current.add(work); 118 if (token is SpaceToken && token.breakWeight == weight
119 && i < tokens.length - 1) {
120 LineToken nextToken = tokens[i + 1];
121 if (length + token.length + nextToken.length > maxLength) {
122 newChunks.add(chunk.subChunk(newIndent, start, i));
123 newIndent = chunk.indent + 2;
124 start = i + 1;
125 length = 0;
126 continue;
127 }
128 }
129 length += token.length;
130 }
131 if (start < tokens.length) {
132 newChunks.add(chunk.subChunk(newIndent, start));
133 }
134 } else {
135 List<LineToken> part = [];
136 int start = 0;
137 for (int i = 0; i < tokens.length; i++) {
138 LineToken token = tokens[i];
139 if (token is SpaceToken && token.breakWeight == weight) {
140 newChunks.add(chunk.subChunk(newIndent, start, i));
141 newIndent = chunk.indent + 2;
142 start = i + 1;
143 }
144 }
145 if (start < tokens.length) {
146 newChunks.add(chunk.subChunk(newIndent, start));
147 }
148 }
149 } else {
150 newChunks.add(chunk);
151 }
117 } else { 152 } else {
118 if (current.length > 0) { 153 newChunks.add(chunk);
119 chunks.add(current);
120 }
121 current = work;
122 } 154 }
123 work = new Chunk(start: tok, maxLength: maxLength - current.length); 155 if (newChunks.length > chunks.length) {
124 } else { 156 hasChanges = true;
125 if (work.fits(tok)) {
126 work.add(tok);
127 } else {
128 if (!isAllWhitespace(work) || isLineStart(current)) {
129 current.add(work);
130 } else if (current.length > 0) {
131 chunks.add(current);
132 current = new Chunk(maxLength: maxLength);
133 }
134 work = new Chunk(maxLength: maxLength);
135 work.add(tok);
136 } 157 }
137 } 158 }
138 159 if (!hasChanges) {
139 }); 160 break;
140 161 }
141 current.add(work); 162 chunks = newChunks;
142 if (current.length > 0) {
143 chunks.add(current);
144 } 163 }
145 return chunks; 164 return chunks;
146 } 165 }
147 166
148 static List<LineToken> preprocess(List<LineToken> tok) { 167 static List<LineToken> preprocess(List<LineToken> tok) {
149 168
150 var tokens = <LineToken>[]; 169 var tokens = <LineToken>[];
151 var curr; 170 var curr;
152 171
153 tok.forEach((token){ 172 tok.forEach((token) {
154 if (token is! SpaceToken) { 173 if (token is! SpaceToken) {
155 if (curr == null) { 174 if (curr == null) {
156 curr = token; 175 curr = token;
157 } else { 176 } else {
158 curr = merge(curr, token); 177 curr = merge(curr, token);
159 } 178 }
160 } else { 179 } else {
161 if (isNonbreaking(token)) { 180 if (isNonbreaking(token)) {
162 curr = merge(curr, token); 181 curr = merge(curr, token);
163 } else { 182 } else {
(...skipping 11 matching lines...) Expand all
175 } 194 }
176 195
177 return tokens; 196 return tokens;
178 } 197 }
179 198
180 static bool isNonbreaking(SpaceToken token) => 199 static bool isNonbreaking(SpaceToken token) =>
181 token.breakWeight == UNBREAKABLE_SPACE_WEIGHT; 200 token.breakWeight == UNBREAKABLE_SPACE_WEIGHT;
182 201
183 static LineToken merge(LineToken first, LineToken second) => 202 static LineToken merge(LineToken first, LineToken second) =>
184 new LineToken(first.value + second.value); 203 new LineToken(first.value + second.value);
185
186 bool isAllWhitespace(Chunk chunk) => isWhitespace(chunk.buffer.toString());
187
188 bool isLineStart(chunk) => chunk.length == 0 && chunk.start == LINE_START;
189
190 /// Test whether this token is a good start for a new working chunk
191 bool goodStart(LineToken tok, Chunk workingChunk) =>
192 tok is SpaceToken && tok.breakWeight >= workingChunk.start.breakWeight;
193
194 } 204 }
195 205
196 /// Test if this [string] contains only whitespace characters 206 /// Test if this [string] contains only whitespace characters
197 bool isWhitespace(String string) => string.codeUnits.every( 207 bool isWhitespace(String string) => string.codeUnits.every(
198 (c) => c == 0x09 || c == 0x20 || c == 0x0A || c == 0x0D); 208 (c) => c == 0x09 || c == 0x20 || c == 0x0A || c == 0x0D);
199 209
200 /// Special token indicating a line start 210 /// Special token indicating a line start
201 final LINE_START = new SpaceToken(0); 211 final LINE_START = new SpaceToken(0);
202 212
203 const DEFAULT_SPACE_WEIGHT = 0; 213 const DEFAULT_SPACE_WEIGHT = UNBREAKABLE_SPACE_WEIGHT - 1;
204 const UNBREAKABLE_SPACE_WEIGHT = -1; 214 const UNBREAKABLE_SPACE_WEIGHT = 1000;
205 215
206 /// Simple non-breaking printer 216 /// Simple non-breaking printer
207 class SimpleLinePrinter extends LinePrinter { 217 class SimpleLinePrinter extends LinePrinter {
208 218
209 const SimpleLinePrinter(); 219 const SimpleLinePrinter();
210 220
211 String printLine(Line line) { 221 String printLine(Line line) {
212 var buffer = new StringBuffer(); 222 var buffer = new StringBuffer();
213 line.tokens.forEach((tok) => buffer.write(tok.toString())); 223 line.tokens.forEach((tok) => buffer.write(tok.toString()));
214 return buffer.toString(); 224 return buffer.toString();
215 } 225 }
216 226
217 } 227 }
218 228
219 229
220 /// Describes a piece of text in a [Line]. 230 /// Describes a piece of text in a [Line].
221 abstract class LineText { 231 abstract class LineText {
222 int get length; 232 int get length;
223 void addTo(Chunk chunk);
224 } 233 }
225 234
226 235
227 /// A working piece of text used in calculating line breaks 236 /// A working piece of text used in calculating line breaks
228 class Chunk implements LineText { 237 class Chunk {
238 final int indent;
239 final int maxLength;
240 final List<LineToken> tokens = <LineToken>[];
229 241
230 final StringBuffer buffer = new StringBuffer(); 242 Chunk(this.indent, this.maxLength, [List<LineToken> tokens]) {
231 243 this.tokens.addAll(tokens);
232 int maxLength;
233 SpaceToken start;
234
235 Chunk({this.start, this.maxLength}) {
236 if (start == null) {
237 start = LINE_START;
238 }
239 } 244 }
240 245
241 bool fits(LineText text) => length + text.length <= maxLength; 246 int get length => tokens.fold(0, (len, token) => len + token.length);
242 247
243 int get length => start.value.length + buffer.length; 248 bool fits(LineToken a, LineToken b) {
244 249 return length + a.length + a.length <= maxLength;
245 void add(LineText text) {
246 text.addTo(this);
247 } 250 }
248 251
249 String toString() => buffer.toString(); 252 void add(LineToken token) {
253 tokens.add(token);
254 }
250 255
251 void addTo(Chunk chunk) { 256 bool hasAnySpace() {
252 chunk.buffer.write(start.value); 257 return tokens.any((token) => token is SpaceToken);
253 chunk.buffer.write(buffer.toString());
254 } 258 }
259
260 int findMinSpaceWeight() {
261 int minWeight = UNBREAKABLE_SPACE_WEIGHT;
262 for (var token in tokens) {
263 if (token is SpaceToken) {
264 minWeight = math.min(minWeight, token.breakWeight);
265 }
266 }
267 return minWeight;
268 }
269
270 Chunk subChunk(int indentLevel, int start, [int end]) {
271 List<LineToken> subTokens = tokens.sublist(start, end);
272 return new Chunk(indentLevel, maxLength, subTokens);
273 }
274
275 String toString() => tokens.join();
255 } 276 }
256 277
257 278
258 class LineToken implements LineText { 279 class LineToken implements LineText {
259 280
260 final String value; 281 final String value;
261 282
262 LineToken(this.value); 283 LineToken(this.value);
263 284
264 String toString() => value; 285 String toString() => value;
265 286
266 int get length => lengthLessNewlines(value); 287 int get length => lengthLessNewlines(value);
267 288
268 void addTo(Chunk chunk) {
269 chunk.buffer.write(value);
270 }
271
272 int lengthLessNewlines(String str) => 289 int lengthLessNewlines(String str) =>
273 str.endsWith('\n') ? str.length - 1 : str.length; 290 str.endsWith('\n') ? str.length - 1 : str.length;
274 291
275 } 292 }
276 293
277 294
278 class SpaceToken extends LineToken { 295 class SpaceToken extends LineToken {
279 296
280 final int breakWeight; 297 final int breakWeight;
281 298
(...skipping 164 matching lines...) Expand 10 before | Expand all | Expand 10 after
446 463
447 String getTabs(int n) => n < TABS.length ? TABS[n] : repeat('\t', n); 464 String getTabs(int n) => n < TABS.length ? TABS[n] : repeat('\t', n);
448 465
449 String repeat(String ch, int times) { 466 String repeat(String ch, int times) {
450 var sb = new StringBuffer(); 467 var sb = new StringBuffer();
451 for (var i = 0; i < times; ++i) { 468 for (var i = 0; i < times; ++i) {
452 sb.write(ch); 469 sb.write(ch);
453 } 470 }
454 return sb.toString(); 471 return sb.toString();
455 } 472 }
OLDNEW
« no previous file with comments | « pkg/analyzer/lib/src/services/formatter_impl.dart ('k') | pkg/analyzer/test/services/data/stmt_tests.data » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698