blob: 26bea118cfe209ac8a7eff871b39bf73190f03e1 [file] [log] [blame]
// 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 _cyan = _color("\u001b[36m");
final _gray = _color("\u001b[1;30m");
final _green = _color("\u001b[32m");
final _red = _color("\u001b[31m");
final _magenta = _color("\u001b[35m");
final _none = _color("\u001b[0m");
final _noColor = _color("\u001b[39m");
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 : "";