| // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| /// Internal debugging utilities. |
| library dart_style.src.debug; |
| |
| import 'dart:math' as math; |
| |
| import 'chunk.dart'; |
| import 'line_splitting/rule_set.dart'; |
| |
| /// Set this to `true` to turn on diagnostic output while building chunks. |
| bool traceChunkBuilder = false; |
| |
| /// Set this to `true` to turn on diagnostic output while writing lines. |
| bool traceLineWriter = false; |
| |
| /// Set this to `true` to turn on diagnostic output while line splitting. |
| bool traceSplitter = false; |
| |
| bool useAnsiColors = false; |
| |
| const unicodeSection = "\u00a7"; |
| const unicodeMidDot = "\u00b7"; |
| |
| /// The whitespace prefixing each line of output. |
| String _indent = ""; |
| |
| void indent() { |
| _indent = " $_indent"; |
| } |
| |
| void unindent() { |
| _indent = _indent.substring(2); |
| } |
| |
| /// Constants for ANSI color escape codes. |
| final _gray = _color("\u001b[1;30m"); |
| final _green = _color("\u001b[32m"); |
| final _none = _color("\u001b[0m"); |
| final _bold = _color("\u001b[1m"); |
| |
| /// Prints [message] to stdout with each line correctly indented. |
| void log([message]) { |
| if (message == null) { |
| print(""); |
| return; |
| } |
| |
| print(_indent + message.toString().replaceAll("\n", "\n$_indent")); |
| } |
| |
| /// Wraps [message] in gray ANSI escape codes if enabled. |
| String gray(message) => "$_gray$message$_none"; |
| |
| /// Wraps [message] in green ANSI escape codes if enabled. |
| String green(message) => "$_green$message$_none"; |
| |
| /// Wraps [message] in bold ANSI escape codes if enabled. |
| String bold(message) => "$_bold$message$_none"; |
| |
| /// Prints [chunks] to stdout, one chunk per line, with detailed information |
| /// about each chunk. |
| void dumpChunks(int start, List<Chunk> chunks) { |
| if (chunks.skip(start).isEmpty) return; |
| |
| // Show the spans as vertical bands over their range (unless there are too |
| // many). |
| var spanSet = new Set<Span>(); |
| addSpans(List<Chunk> chunks) { |
| for (var chunk in chunks) { |
| spanSet.addAll(chunk.spans); |
| |
| if (chunk.isBlock) addSpans(chunk.block.chunks); |
| } |
| } |
| |
| addSpans(chunks); |
| |
| var spans = spanSet.toList(); |
| var rules = |
| chunks.map((chunk) => chunk.rule).where((rule) => rule != null).toSet(); |
| |
| var rows = <List<String>>[]; |
| |
| addChunk(List<Chunk> chunks, String prefix, int index) { |
| var row = <String>[]; |
| row.add("$prefix$index:"); |
| |
| var chunk = chunks[index]; |
| if (chunk.text.length > 70) { |
| row.add(chunk.text.substring(0, 70)); |
| } else { |
| row.add(chunk.text); |
| } |
| |
| if (spans.length <= 20) { |
| var spanBars = ""; |
| for (var span in spans) { |
| if (chunk.spans.contains(span)) { |
| if (index == 0 || !chunks[index - 1].spans.contains(span)) { |
| if (span.cost == 1) { |
| spanBars += "╖"; |
| } else { |
| spanBars += span.cost.toString(); |
| } |
| } else { |
| spanBars += "║"; |
| } |
| } else { |
| if (index > 0 && chunks[index - 1].spans.contains(span)) { |
| spanBars += "╜"; |
| } else { |
| spanBars += " "; |
| } |
| } |
| } |
| row.add(spanBars); |
| } |
| |
| writeIf(predicate, String callback()) { |
| if (predicate) { |
| row.add(callback()); |
| } else { |
| row.add(""); |
| } |
| } |
| |
| if (chunk.rule == null) { |
| row.add(""); |
| row.add("(no rule)"); |
| row.add(""); |
| } else { |
| writeIf(chunk.rule.cost != 0, () => "\$${chunk.rule.cost}"); |
| |
| var ruleString = chunk.rule.toString(); |
| if (chunk.rule.isHardened) ruleString += "!"; |
| row.add(ruleString); |
| |
| var constrainedRules = |
| chunk.rule.constrainedRules.toSet().intersection(rules); |
| writeIf(constrainedRules.isNotEmpty, |
| () => "-> ${constrainedRules.join(" ")}"); |
| } |
| |
| writeIf(chunk.indent != null && chunk.indent != 0, |
| () => "indent ${chunk.indent}"); |
| |
| writeIf(chunk.nesting != null && chunk.nesting.indent != 0, |
| () => "nest ${chunk.nesting}"); |
| |
| writeIf(chunk.flushLeft != null && chunk.flushLeft, () => "flush"); |
| |
| writeIf(chunk.canDivide, () => "divide"); |
| |
| rows.add(row); |
| |
| if (chunk.isBlock) { |
| for (var j = 0; j < chunk.block.chunks.length; j++) { |
| addChunk(chunk.block.chunks, "$prefix$index.", j); |
| } |
| } |
| } |
| |
| for (var i = start; i < chunks.length; i++) { |
| addChunk(chunks, "", i); |
| } |
| |
| var rowWidths = new List.filled(rows.first.length, 0); |
| for (var row in rows) { |
| for (var i = 0; i < row.length; i++) { |
| rowWidths[i] = math.max(rowWidths[i], row[i].length); |
| } |
| } |
| |
| var buffer = new StringBuffer(); |
| for (var row in rows) { |
| for (var i = 0; i < row.length; i++) { |
| var cell = row[i].padRight(rowWidths[i]); |
| |
| if (i != 1) cell = gray(cell); |
| |
| buffer.write(cell); |
| buffer.write(" "); |
| } |
| |
| buffer.writeln(); |
| } |
| |
| print(buffer.toString()); |
| } |
| |
| /// Shows all of the constraints between the rules used by [chunks]. |
| void dumpConstraints(List<Chunk> chunks) { |
| var rules = |
| chunks.map((chunk) => chunk.rule).where((rule) => rule != null).toSet(); |
| |
| for (var rule in rules) { |
| var constrainedValues = []; |
| for (var value = 0; value < rule.numValues; value++) { |
| var constraints = []; |
| for (var other in rules) { |
| if (rule == other) continue; |
| |
| var constraint = rule.constrain(value, other); |
| if (constraint != null) { |
| constraints.add("$other->$constraint"); |
| } |
| } |
| |
| if (constraints.isNotEmpty) { |
| constrainedValues.add("$value:(${constraints.join(' ')})"); |
| } |
| } |
| |
| log("$rule ${constrainedValues.join(' ')}"); |
| } |
| } |
| |
| /// Convert the line to a [String] representation. |
| /// |
| /// It will determine how best to split it into multiple lines of output and |
| /// return a single string that may contain one or more newline characters. |
| void dumpLines(List<Chunk> chunks, int firstLineIndent, SplitSet splits) { |
| var buffer = new StringBuffer(); |
| |
| writeIndent(indent) => buffer.write(gray("| " * (indent ~/ 2))); |
| |
| writeChunksUnsplit(List<Chunk> chunks) { |
| for (var chunk in chunks) { |
| buffer.write(chunk.text); |
| if (chunk.spaceWhenUnsplit) buffer.write(" "); |
| |
| // Recurse into the block. |
| if (chunk.isBlock) writeChunksUnsplit(chunk.block.chunks); |
| } |
| } |
| |
| writeIndent(firstLineIndent); |
| |
| for (var i = 0; i < chunks.length - 1; i++) { |
| var chunk = chunks[i]; |
| buffer.write(chunk.text); |
| |
| if (splits.shouldSplitAt(i)) { |
| for (var j = 0; j < (chunk.isDouble ? 2 : 1); j++) { |
| buffer.writeln(); |
| writeIndent(splits.getColumn(i)); |
| } |
| } else { |
| if (chunk.isBlock) writeChunksUnsplit(chunk.block.chunks); |
| |
| if (chunk.spaceWhenUnsplit) buffer.write(" "); |
| } |
| } |
| |
| buffer.write(chunks.last.text); |
| log(buffer); |
| } |
| |
| String _color(String ansiEscape) => useAnsiColors ? ansiEscape : ""; |