Chromium Code Reviews| Index: pkg/analyzer/lib/src/services/writer.dart |
| diff --git a/pkg/analyzer/lib/src/services/writer.dart b/pkg/analyzer/lib/src/services/writer.dart |
| index cf0dd74718aaa05917a1b55124c04cfadd0987a8..94a3bc9675a203c7cfa0e84a7e6224a974258b02 100644 |
| --- a/pkg/analyzer/lib/src/services/writer.dart |
| +++ b/pkg/analyzer/lib/src/services/writer.dart |
| @@ -84,63 +84,82 @@ class SimpleLineBreaker extends LinePrinter { |
| var buf = new StringBuffer(); |
| var chunks = breakLine(line); |
| for (var i = 0; i < chunks.length; ++i) { |
| + var chunk = chunks[i]; |
| if (i > 0) { |
| - buf.write(indent(chunks[i], line.indentLevel)); |
| + buf.write(indent(chunk, chunk.indent)); |
| } else { |
| - buf.write(chunks[i]); |
| + buf.write(chunk); |
| } |
| } |
| return buf.toString(); |
| } |
| - String indent(Chunk chunk, int level) => |
| - '\n' + indenter(level + 2) + chunk.toString(); |
| + String indent(Chunk chunk, int level) { |
| + return '\n' + indenter(level) + chunk.toString(); |
| + } |
| List<Chunk> breakLine(Line line) { |
| - |
| - var tokens = preprocess(line.tokens); |
| - |
| - var chunks = <Chunk>[]; |
| - |
| - // The current unbroken line |
| - var current = new Chunk(maxLength: maxLength); |
| - |
| - // A tentative working chunk that will either start a new line or get |
| - // absorbed into 'current' |
| - var work = new Chunk(maxLength: maxLength); |
| - |
| - tokens.forEach((tok) { |
| - |
| - if (goodStart(tok, work)) { |
| - if (current.fits(work)) { |
| - current.add(work); |
| - } else { |
| - if (current.length > 0) { |
| - chunks.add(current); |
| + List<LineToken> tokens = preprocess(line.tokens); |
| + List<Chunk> chunks = <Chunk>[new Chunk(line.indentLevel, maxLength, tokens)]; |
| + while (true) { |
| + List<Chunk> newChunks = <Chunk>[]; |
| + bool hasChanges = false; |
| + for (Chunk chunk in chunks) { |
| + tokens = chunk.tokens; |
| + if (chunk.length > maxLength) { |
| + if (chunk.hasAnySpace()) { |
| + int weight = chunk.findMinSpaceWeight(); |
| + int newIndent = chunk.indent; |
| + if (weight == DEFAULT_SPACE_WEIGHT) { |
| + int start = 0; |
| + int length = 0; |
| + for (int i = 0; i < tokens.length; i++) { |
| + LineToken token = tokens[i]; |
| + if (token is SpaceToken && token.breakWeight == weight |
| + && i < tokens.length - 1) { |
| + LineToken nextToken = tokens[i + 1]; |
| + if (length + token.length + nextToken.length > maxLength) { |
| + newChunks.add(chunk.subChunk(newIndent, start, i)); |
| + newIndent = chunk.indent + 2; |
| + start = i + 1; |
| + length = 0; |
| + continue; |
| + } |
| + } |
| + length += token.length; |
| + } |
| + if (start < tokens.length) { |
| + newChunks.add(chunk.subChunk(newIndent, start)); |
| + } |
| + } else { |
| + List<LineToken> part = []; |
| + int start = 0; |
| + for (int i = 0; i < tokens.length; i++) { |
| + LineToken token = tokens[i]; |
| + if (token is SpaceToken && token.breakWeight == weight) { |
| + newChunks.add(chunk.subChunk(newIndent, start, i)); |
| + newIndent = chunk.indent + 2; |
| + start = i + 1; |
| + } |
| + } |
| + if (start < tokens.length) { |
| + newChunks.add(chunk.subChunk(newIndent, start)); |
| + } |
| + } |
| + } else { |
| + newChunks.add(chunk); |
| } |
| - current = work; |
| - } |
| - work = new Chunk(start: tok, maxLength: maxLength - current.length); |
| - } else { |
| - if (work.fits(tok)) { |
| - work.add(tok); |
| } else { |
| - if (!isAllWhitespace(work) || isLineStart(current)) { |
| - current.add(work); |
| - } else if (current.length > 0) { |
| - chunks.add(current); |
| - current = new Chunk(maxLength: maxLength); |
| - } |
| - work = new Chunk(maxLength: maxLength); |
| - work.add(tok); |
| + newChunks.add(chunk); |
| + } |
| + if (newChunks.length > chunks.length) { |
| + hasChanges = true; |
| } |
| } |
| - |
| - }); |
| - |
| - current.add(work); |
| - if (current.length > 0) { |
| - chunks.add(current); |
| + if (!hasChanges) { |
| + break; |
| + } |
| + chunks = newChunks; |
| } |
| return chunks; |
| } |
| @@ -150,7 +169,7 @@ class SimpleLineBreaker extends LinePrinter { |
| var tokens = <LineToken>[]; |
| var curr; |
| - tok.forEach((token){ |
| + tok.forEach((token) { |
| if (token is! SpaceToken) { |
| if (curr == null) { |
| curr = token; |
| @@ -182,15 +201,6 @@ class SimpleLineBreaker extends LinePrinter { |
| static LineToken merge(LineToken first, LineToken second) => |
| new LineToken(first.value + second.value); |
| - |
| - bool isAllWhitespace(Chunk chunk) => isWhitespace(chunk.buffer.toString()); |
| - |
| - bool isLineStart(chunk) => chunk.length == 0 && chunk.start == LINE_START; |
| - |
| - /// Test whether this token is a good start for a new working chunk |
| - bool goodStart(LineToken tok, Chunk workingChunk) => |
| - tok is SpaceToken && tok.breakWeight >= workingChunk.start.breakWeight; |
| - |
| } |
| /// Test if this [string] contains only whitespace characters |
| @@ -200,8 +210,8 @@ bool isWhitespace(String string) => string.codeUnits.every( |
| /// Special token indicating a line start |
| final LINE_START = new SpaceToken(0); |
| -const DEFAULT_SPACE_WEIGHT = 0; |
| -const UNBREAKABLE_SPACE_WEIGHT = -1; |
| +const DEFAULT_SPACE_WEIGHT = UNBREAKABLE_SPACE_WEIGHT - 1; |
| +const UNBREAKABLE_SPACE_WEIGHT = 1000; |
| /// Simple non-breaking printer |
| class SimpleLinePrinter extends LinePrinter { |
| @@ -220,38 +230,58 @@ class SimpleLinePrinter extends LinePrinter { |
| /// Describes a piece of text in a [Line]. |
| abstract class LineText { |
| int get length; |
| - void addTo(Chunk chunk); |
| } |
| /// A working piece of text used in calculating line breaks |
| -class Chunk implements LineText { |
| +class Chunk { |
| + final int indent; |
| + final int maxLength; |
| + final List<LineToken> tokens = <LineToken>[]; |
| - final StringBuffer buffer = new StringBuffer(); |
| + Chunk(this.indent, this.maxLength, [List<LineToken> tokens]) { |
| + this.tokens.addAll(tokens); |
| + } |
| - int maxLength; |
| - SpaceToken start; |
| + int get length => tokens.fold(0, (len, token) => len + token.length); |
| - Chunk({this.start, this.maxLength}) { |
| - if (start == null) { |
| - start = LINE_START; |
| - } |
| + bool fits(LineToken a, LineToken b) { |
| + return length + a.length + a.length <= maxLength; |
| } |
| - bool fits(LineText text) => length + text.length <= maxLength; |
| + void add(LineToken token) { |
| + tokens.add(token); |
| + } |
| - int get length => start.value.length + buffer.length; |
| + bool hasAnySpace() { |
| + return tokens.any((token) => token is SpaceToken); |
| + } |
| - void add(LineText text) { |
| - text.addTo(this); |
| + bool hasSpaceTokenWithWeight(int weight) { |
| + for (var token in tokens) { |
| + if (token is SpaceToken && token.breakWeight == weight) { |
| + return true; |
| + } |
| + } |
| + return false; |
| } |
| - String toString() => buffer.toString(); |
| + int findMinSpaceWeight() { |
|
Brian Wilkerson
2014/07/09 19:47:15
This seems really inefficient. And what happens if
scheglov
2014/07/09 20:01:41
Ouch... Forgot to optimize it.
Thanks!
|
| + int weight = 0; |
| + while (true) { |
| + if (hasSpaceTokenWithWeight(weight)) { |
| + return weight; |
| + } |
| + weight++; |
| + } |
| + } |
| - void addTo(Chunk chunk) { |
| - chunk.buffer.write(start.value); |
| - chunk.buffer.write(buffer.toString()); |
| + Chunk subChunk(int indentLevel, int start, [int end]) { |
| + List<LineToken> subTokens = tokens.sublist(start, end); |
| + return new Chunk(indentLevel, maxLength, subTokens); |
| } |
| + |
| + String toString() => tokens.join(); |
| } |
| @@ -265,10 +295,6 @@ class LineToken implements LineText { |
| int get length => lengthLessNewlines(value); |
| - void addTo(Chunk chunk) { |
| - chunk.buffer.write(value); |
| - } |
| - |
| int lengthLessNewlines(String str) => |
| str.endsWith('\n') ? str.length - 1 : str.length; |