| 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 | | /// Internal debugging utilities.
|
| 6 | | library dart_style.src.debug;
|
| 7 | |
|
| 8 | | import 'dart:math' as math;
|
| 9 | |
|
| 10 | | import 'chunk.dart';
|
| 11 | | import 'line_splitting/rule_set.dart';
|
| 12 | |
|
| 13 | | /// Set this to `true` to turn on diagnostic output while building chunks.
|
| 14 | | bool traceChunkBuilder = false;
|
| 15 | |
|
| 16 | | /// Set this to `true` to turn on diagnostic output while writing lines.
|
| 17 | | bool traceLineWriter = false;
|
| 18 | |
|
| 19 | | /// Set this to `true` to turn on diagnostic output while line splitting.
|
| 20 | | bool traceSplitter = false;
|
| 21 | |
|
| 22 | | bool useAnsiColors = false;
|
| 23 | |
|
| 24 | | const unicodeSection = "\u00a7";
|
| 25 | | const unicodeMidDot = "\u00b7";
|
| 26 | |
|
| 27 | | /// The whitespace prefixing each line of output.
|
| 28 | | String _indent = "";
|
| 29 | |
|
| 30 | 0 | void indent() {
|
| 31 | 0 | _indent = " $_indent";
|
| 32 | | }
|
| 33 | |
|
| 34 | 0 | void unindent() {
|
| 35 | 0 | _indent = _indent.substring(2);
|
| 36 | | }
|
| 37 | |
|
| 38 | | /// Constants for ANSI color escape codes.
|
| 39 | | final _gray = _color("\u001b[1;30m");
|
| 40 | | final _green = _color("\u001b[32m");
|
| 41 | | final _none = _color("\u001b[0m");
|
| 42 | | final _bold = _color("\u001b[1m");
|
| 43 | |
|
| 44 | | /// Prints [message] to stdout with each line correctly indented.
|
| 45 | 0 | void log([message]) {
|
| 46 | | if (message == null) {
|
| 47 | 0 | print("");
|
| 48 | | return;
|
| 49 | | }
|
| 50 | |
|
| 51 | 0 | print(_indent + message.toString().replaceAll("\n", "\n$_indent"));
|
| 52 | | }
|
| 53 | |
|
| 54 | | /// Wraps [message] in gray ANSI escape codes if enabled.
|
| 55 | 0 | String gray(message) => "$_gray$message$_none";
|
| 56 | |
|
| 57 | | /// Wraps [message] in green ANSI escape codes if enabled.
|
| 58 | 0 | String green(message) => "$_green$message$_none";
|
| 59 | |
|
| 60 | | /// Wraps [message] in bold ANSI escape codes if enabled.
|
| 61 | 0 | String bold(message) => "$_bold$message$_none";
|
| 62 | |
|
| 63 | | /// Prints [chunks] to stdout, one chunk per line, with detailed information
|
| 64 | | /// about each chunk.
|
| 65 | 0 | void dumpChunks(int start, List<Chunk> chunks) {
|
| 66 | 0 | if (chunks.skip(start).isEmpty) return;
|
| 67 | |
|
| 68 | | // Show the spans as vertical bands over their range (unless there are too
|
| 69 | | // many).
|
| 70 | 0 | var spanSet = new Set<Span>();
|
| 71 | 0 | addSpans(List<Chunk> chunks) {
|
| 72 | 0 | for (var chunk in chunks) {
|
| 73 | 0 | spanSet.addAll(chunk.spans);
|
| 74 | |
|
| 75 | 0 | if (chunk.isBlock) addSpans(chunk.block.chunks);
|
| 76 | | }
|
| 77 | | }
|
| 78 | |
|
| 79 | 0 | addSpans(chunks);
|
| 80 | |
|
| 81 | 0 | var spans = spanSet.toList();
|
| 82 | | var rules =
|
| 83 | 0 | chunks.map((chunk) => chunk.rule).where((rule) => rule != null).toSet();
|
| 84 | |
|
| 85 | 0 | var rows = <List<String>>[];
|
| 86 | |
|
| 87 | 0 | addChunk(List<Chunk> chunks, String prefix, int index) {
|
| 88 | 0 | var row = <String>[];
|
| 89 | 0 | row.add("$prefix$index:");
|
| 90 | |
|
| 91 | 0 | var chunk = chunks[index];
|
| 92 | 0 | if (chunk.text.length > 70) {
|
| 93 | 0 | row.add(chunk.text.substring(0, 70));
|
| 94 | | } else {
|
| 95 | 0 | row.add(chunk.text);
|
| 96 | | }
|
| 97 | |
|
| 98 | 0 | if (spans.length <= 20) {
|
| 99 | | var spanBars = "";
|
| 100 | 0 | for (var span in spans) {
|
| 101 | 0 | if (chunk.spans.contains(span)) {
|
| 102 | 0 | if (index == 0 || !chunks[index - 1].spans.contains(span)) {
|
| 103 | 0 | if (span.cost == 1) {
|
| 104 | 0 | spanBars += "╖";
|
| 105 | | } else {
|
| 106 | 0 | spanBars += span.cost.toString();
|
| 107 | | }
|
| 108 | | } else {
|
| 109 | 0 | spanBars += "║";
|
| 110 | | }
|
| 111 | | } else {
|
| 112 | 0 | if (index > 0 && chunks[index - 1].spans.contains(span)) {
|
| 113 | 0 | spanBars += "╜";
|
| 114 | | } else {
|
| 115 | 0 | spanBars += " ";
|
| 116 | | }
|
| 117 | | }
|
| 118 | | }
|
| 119 | 0 | row.add(spanBars);
|
| 120 | | }
|
| 121 | |
|
| 122 | 0 | writeIf(predicate, String callback()) {
|
| 123 | | if (predicate) {
|
| 124 | 0 | row.add(callback());
|
| 125 | | } else {
|
| 126 | 0 | row.add("");
|
| 127 | | }
|
| 128 | | }
|
| 129 | |
|
| 130 | 0 | if (chunk.rule == null) {
|
| 131 | 0 | row.add("");
|
| 132 | 0 | row.add("(no rule)");
|
| 133 | 0 | row.add("");
|
| 134 | | } else {
|
| 135 | 0 | writeIf(chunk.rule.cost != 0, () => "\$${chunk.rule.cost}");
|
| 136 | |
|
| 137 | 0 | var ruleString = chunk.rule.toString();
|
| 138 | 0 | if (chunk.rule.isHardened) ruleString += "!";
|
| 139 | 0 | row.add(ruleString);
|
| 140 | |
|
| 141 | | var constrainedRules =
|
| 142 | 0 | chunk.rule.constrainedRules.toSet().intersection(rules);
|
| 143 | 0 | writeIf(constrainedRules.isNotEmpty,
|
| 144 | 0 | () => "-> ${constrainedRules.join(" ")}");
|
| 145 | | }
|
| 146 | |
|
| 147 | 0 | writeIf(chunk.indent != null && chunk.indent != 0,
|
| 148 | 0 | () => "indent ${chunk.indent}");
|
| 149 | |
|
| 150 | 0 | writeIf(chunk.nesting != null && chunk.nesting.indent != 0,
|
| 151 | 0 | () => "nest ${chunk.nesting}");
|
| 152 | |
|
| 153 | 0 | writeIf(chunk.flushLeft != null && chunk.flushLeft, () => "flush");
|
| 154 | |
|
| 155 | 0 | writeIf(chunk.canDivide, () => "divide");
|
| 156 | |
|
| 157 | 0 | rows.add(row);
|
| 158 | |
|
| 159 | 0 | if (chunk.isBlock) {
|
| 160 | 0 | for (var j = 0; j < chunk.block.chunks.length; j++) {
|
| 161 | 0 | addChunk(chunk.block.chunks, "$prefix$index.", j);
|
| 162 | | }
|
| 163 | | }
|
| 164 | | }
|
| 165 | |
|
| 166 | 0 | for (var i = start; i < chunks.length; i++) {
|
| 167 | 0 | addChunk(chunks, "", i);
|
| 168 | | }
|
| 169 | |
|
| 170 | 0 | var rowWidths = new List.filled(rows.first.length, 0);
|
| 171 | 0 | for (var row in rows) {
|
| 172 | 0 | for (var i = 0; i < row.length; i++) {
|
| 173 | 5 | rowWidths[i] = math.max(rowWidths[i], row[i].length);
|
| 174 | | }
|
| 175 | | }
|
| 176 | |
|
| 177 | 0 | var buffer = new StringBuffer();
|
| 178 | 0 | for (var row in rows) {
|
| 179 | 0 | for (var i = 0; i < row.length; i++) {
|
| 180 | 0 | var cell = row[i].padRight(rowWidths[i]);
|
| 181 | |
|
| 182 | 0 | if (i != 1) cell = gray(cell);
|
| 183 | |
|
| 184 | 0 | buffer.write(cell);
|
| 185 | 0 | buffer.write(" ");
|
| 186 | | }
|
| 187 | |
|
| 188 | 0 | buffer.writeln();
|
| 189 | | }
|
| 190 | |
|
| 191 | 0 | print(buffer.toString());
|
| 192 | | }
|
| 193 | |
|
| 194 | | /// Shows all of the constraints between the rules used by [chunks].
|
| 195 | 0 | void dumpConstraints(List<Chunk> chunks) {
|
| 196 | | var rules =
|
| 197 | 0 | chunks.map((chunk) => chunk.rule).where((rule) => rule != null).toSet();
|
| 198 | |
|
| 199 | 0 | for (var rule in rules) {
|
| 200 | 0 | var constrainedValues = [];
|
| 201 | 0 | for (var value = 0; value < rule.numValues; value++) {
|
| 202 | 0 | var constraints = [];
|
| 203 | 0 | for (var other in rules) {
|
| 204 | 0 | if (rule == other) continue;
|
| 205 | |
|
| 206 | 0 | var constraint = rule.constrain(value, other);
|
| 207 | | if (constraint != null) {
|
| 208 | 0 | constraints.add("$other->$constraint");
|
| 209 | | }
|
| 210 | | }
|
| 211 | |
|
| 212 | 0 | if (constraints.isNotEmpty) {
|
| 213 | 0 | constrainedValues.add("$value:(${constraints.join(' ')})");
|
| 214 | | }
|
| 215 | | }
|
| 216 | |
|
| 217 | 0 | log("$rule ${constrainedValues.join(' ')}");
|
| 218 | | }
|
| 219 | | }
|
| 220 | |
|
| 221 | | /// Convert the line to a [String] representation.
|
| 222 | | ///
|
| 223 | | /// It will determine how best to split it into multiple lines of output and
|
| 224 | | /// return a single string that may contain one or more newline characters.
|
| 225 | 0 | void dumpLines(List<Chunk> chunks, int firstLineIndent, SplitSet splits) {
|
| 226 | 0 | var buffer = new StringBuffer();
|
| 227 | |
|
| 228 | 0 | writeIndent(indent) => buffer.write(gray("| " * (indent ~/ 2)));
|
| 229 | |
|
| 230 | 0 | writeChunksUnsplit(List<Chunk> chunks) {
|
| 231 | 0 | for (var chunk in chunks) {
|
| 232 | 0 | buffer.write(chunk.text);
|
| 233 | 0 | if (chunk.spaceWhenUnsplit) buffer.write(" ");
|
| 234 | |
|
| 235 | | // Recurse into the block.
|
| 236 | 0 | if (chunk.isBlock) writeChunksUnsplit(chunk.block.chunks);
|
| 237 | | }
|
| 238 | | }
|
| 239 | |
|
| 240 | 0 | writeIndent(firstLineIndent);
|
| 241 | |
|
| 242 | 0 | for (var i = 0; i < chunks.length - 1; i++) {
|
| 243 | 0 | var chunk = chunks[i];
|
| 244 | 0 | buffer.write(chunk.text);
|
| 245 | |
|
| 246 | 0 | if (splits.shouldSplitAt(i)) {
|
| 247 | 0 | for (var j = 0; j < (chunk.isDouble ? 2 : 1); j++) {
|
| 248 | 0 | buffer.writeln();
|
| 249 | 0 | writeIndent(splits.getColumn(i));
|
| 250 | | }
|
| 251 | | } else {
|
| 252 | 0 | if (chunk.isBlock) writeChunksUnsplit(chunk.block.chunks);
|
| 253 | |
|
| 254 | 0 | if (chunk.spaceWhenUnsplit) buffer.write(" ");
|
| 255 | | }
|
| 256 | | }
|
| 257 | |
|
| 258 | 0 | buffer.write(chunks.last.text);
|
| 259 | 0 | log(buffer);
|
| 260 | | }
|
| 261 | |
|
| 262 | 0 | String _color(String ansiEscape) => useAnsiColors ? ansiEscape : "";
|