Coverage report for lib/src/line_writer.dart

Line coverage: 73 / 76 (96.1%)

All files > lib/src/line_writer.dart

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.
473
  int get length => _buffer.length;
48
493
  LineWriter(DartFormatter formatter, this._chunks)
503
      : _lineEnding = formatter.lineEnding,
513
        pageWidth = formatter.pageWidth,
52
        _blockIndentation = 0,
533
        _blockCache = {};
54
55
  /// Creates a line writer for a block.
562
  LineWriter._(this._chunks, this._lineEnding, this.pageWidth,
57
      this._blockIndentation, this._blockCache) {
58
    // There is always a newline after the opening delimiter.
596
    _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.
772
  FormatResult formatBlock(Chunk chunk, int column) {
782
    var key = new _BlockKey(chunk, column);
79
80
    // Use the cached one if we have it.
814
    var cached = _blockCache[key];
82
    if (cached != null) return cached;
83
842
    var writer = new LineWriter._(
8510
        chunk.block.chunks, _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.
904
    var result = writer.writeLines(Indent.block, flushLeft: chunk.flushLeft);
914
    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.
993
  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
10812
    for (var i = 0; i < _chunks.length; i++) {
1096
      var chunk = _chunks[i];
1103
      if (!chunk.canDivide) continue;
111
1122
      totalCost +=
1134
          _completeLine(newlines, indent, start, i + 1, flushLeft: flushLeft);
114
115
      // Get ready for the next line.
1162
      newlines = chunk.isDouble ? 2 : 1;
1172
      indent = chunk.indent;
1182
      flushLeft = chunk.flushLeft;
1192
      start = i + 1;
120
    }
121
1229
    if (start < _chunks.length) {
12312
      totalCost += _completeLine(newlines, indent, start, _chunks.length,
124
          flushLeft: flushLeft);
125
    }
126
127
    // Be a good citizen, end with a newline.
1289
    if (isCompilationUnit) _buffer.write(_lineEnding);
129
1303
    return new FormatResult(
13112
        _buffer.toString(), totalCost, _selectionStart, _selectionEnd);
132
  }
133
134
  /// Takes the chunks from [start] to [end] with leading [indent], removes
135
  /// them, and runs the [LineSplitter] on them.
1363
  int _completeLine(int newlines, int indent, int start, int end,
137
      {bool flushLeft}) {
138
    // Write the newlines required by the previous line.
1395
    for (var j = 0; j < newlines; j++) {
1406
      _buffer.write(_lineEnding);
141
    }
142
1436
    var chunks = _chunks.sublist(start, end);
144
145
    if (debug.traceLineWriter) {
1460
      debug.log(debug.green("\nWriting:"));
1470
      debug.dumpChunks(0, chunks);
1480
      debug.log();
149
    }
150
151
    // Run the line splitter.
1526
    var splitter = new LineSplitter(this, chunks, _blockIndentation, indent,
153
        flushLeft: flushLeft);
1543
    var splits = splitter.apply();
155
156
    // Write the indentation of the first line.
157
    if (!flushLeft) {
15815
      _buffer.write(" " * (indent + _blockIndentation));
159
    }
160
161
    // Write each chunk with the appropriate splits between them.
1629
    for (var i = 0; i < chunks.length; i++) {
1633
      var chunk = chunks[i];
1643
      _writeChunk(chunk);
165
1663
      if (chunk.isBlock) {
1672
        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.
1702
          _writeChunksUnsplit(chunk);
171
        } else {
172
          // Include the formatted block contents.
1734
          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.
1772
          if (block.selectionStart != null) {
1784
            _selectionStart = length + block.selectionStart;
179
          }
180
1812
          if (block.selectionEnd != null) {
1824
            _selectionEnd = length + block.selectionEnd;
183
          }
184
1856
          _buffer.write(block.text);
186
        }
187
      }
188
1899
      if (i == chunks.length - 1) {
190
        // Don't write trailing whitespace after the last chunk.
1913
      } else if (splits.shouldSplitAt(i)) {
1926
        _buffer.write(_lineEnding);
1938
        if (chunk.isDouble) _buffer.write(_lineEnding);
194
1958
        _buffer.write(" " * (splits.getColumn(i)));
196
      } else {
1979
        if (chunk.spaceWhenUnsplit) _buffer.write(" ");
198
      }
199
    }
200
2013
    return splits.cost;
202
  }
203
204
  /// Writes the block chunks of [chunk] (and any child chunks of them,
205
  /// recursively) without any splitting.
2062
  void _writeChunksUnsplit(Chunk chunk) {
2072
    if (!chunk.isBlock) return;
208
2096
    for (var blockChunk in chunk.block.chunks) {
2102
      _writeChunk(blockChunk);
211
2126
      if (blockChunk.spaceWhenUnsplit) _buffer.write(" ");
213
214
      // Recurse into the block.
2152
      _writeChunksUnsplit(blockChunk);
216
    }
217
  }
218
219
  /// Writes [chunk] to the output and updates the selection if the chunk
220
  /// contains a selection marker.
2213
  void _writeChunk(Chunk chunk) {
2223
    if (chunk.selectionStart != null) {
2234
      _selectionStart = length + chunk.selectionStart;
224
    }
225
2263
    if (chunk.selectionEnd != null) {
2274
      _selectionEnd = length + chunk.selectionEnd;
228
    }
229
2309
    _buffer.write(chunk.text);
231
  }
232
}
233
234
/// Key type for the formatted block cache.
235
///
236
/// To cache formatted blocks, we just need to know which block it is (the
237
/// index of its parent chunk) and how far it was indented when we formatted it
238
/// (the starting column).
239
class _BlockKey {
240
  /// The index of the chunk in the surrounding chunk list that contains this
241
  /// block.
242
  final Chunk chunk;
243
244
  /// The absolute zero-based column number where the block starts.
245
  final int column;
246
2472
  _BlockKey(this.chunk, this.column);
248
2492
  bool operator ==(other) {
2502
    if (other is! _BlockKey) return false;
25112
    return chunk == other.chunk && column == other.column;
252
  }
253
25412
  int get hashCode => chunk.hashCode ^ column.hashCode;
255
}
256
257
/// The result of formatting a series of chunks.
258
class FormatResult {
259
  /// The resulting formatted text, including newlines and leading whitespace
260
  /// to reach the proper column.
261
  final String text;
262
263
  /// The numeric cost of the chosen solution.
264
  final int cost;
265
266
  /// Where in the resulting buffer the selection starting point should appear
267
  /// if it was contained within this split list of chunks.
268
  ///
269
  /// Otherwise, this is `null`.
270
  final int selectionStart;
271
272
  /// Where in the resulting buffer the selection end point should appear if it
273
  /// was contained within this split list of chunks.
274
  ///
275
  /// Otherwise, this is `null`.
276
  final int selectionEnd;
277
2783
  FormatResult(this.text, this.cost, this.selectionStart, this.selectionEnd);
279
}