| Index: tests/compiler/dart2js/sourcemaps/sourcemap_html_helper.dart
|
| diff --git a/tests/compiler/dart2js/sourcemaps/sourcemap_html_helper.dart b/tests/compiler/dart2js/sourcemaps/sourcemap_html_helper.dart
|
| index 4227fee724ff4bf7c43a26b202ea79efe0d20070..52e871d7779b8b36daea279e1b41b96b87b2bb69 100644
|
| --- a/tests/compiler/dart2js/sourcemaps/sourcemap_html_helper.dart
|
| +++ b/tests/compiler/dart2js/sourcemaps/sourcemap_html_helper.dart
|
| @@ -8,6 +8,7 @@
|
| library sourcemap.html.helper;
|
|
|
| import 'dart:convert';
|
| +import 'dart:math' as Math;
|
|
|
| import 'package:compiler/src/io/source_file.dart';
|
| import 'package:compiler/src/io/source_information.dart';
|
| @@ -40,9 +41,14 @@ String toPattern(int index) {
|
| return 'linear-gradient(to right, ${startColor.toCss}, ${endColor.toCss})';
|
| }
|
|
|
| -/// Return the html for the [index] line number.
|
| -String lineNumber(int index) {
|
| - return '<span class="lineNumber">${index + 1} </span>';
|
| +/// Return the html for the [index] line number. If [width] is provided, shorter
|
| +/// line numbers will be prefixed with spaces to match the width.
|
| +String lineNumber(int index, [int width]) {
|
| + String text = '${index + 1}';
|
| + if (width != null && text.length < width) {
|
| + text = (' ' * (width - text.length)) + text;
|
| + }
|
| + return '<span class="lineNumber">$text </span>';
|
| }
|
|
|
| /// Return the html escaped [text].
|
| @@ -59,6 +65,10 @@ class SourceMapHtmlInfo {
|
| SourceMapHtmlInfo(this.sourceMapInfo,
|
| this.codeProcessor,
|
| this.sourceLocationCollection);
|
| +
|
| + String toString() {
|
| + return sourceMapInfo.toString();
|
| + }
|
| }
|
|
|
| /// A collection of source locations.
|
| @@ -84,16 +94,98 @@ class SourceLocationCollection {
|
| }
|
| }
|
|
|
| +abstract class CssColorScheme {
|
| + String singleLocationToCssColor(var id);
|
| +
|
| + String multiLocationToCssColor(List ids);
|
| +
|
| + bool get showLocationAsSpan;
|
| +}
|
| +
|
| +class CustomColorScheme implements CssColorScheme {
|
| + final bool showLocationAsSpan;
|
| + final Function single;
|
| + final Function multi;
|
| +
|
| + CustomColorScheme(
|
| + {this.showLocationAsSpan: false,
|
| + String this.single(var id),
|
| + String this.multi(List ids)});
|
| +
|
| + String singleLocationToCssColor(var id) => single != null ? single(id) : null;
|
| +
|
| + String multiLocationToCssColor(List ids) => multi != null ? multi(ids) : null;
|
| +}
|
| +
|
| +class PatternCssColorScheme implements CssColorScheme {
|
| + const PatternCssColorScheme();
|
| +
|
| + bool get showLocationAsSpan => true;
|
| +
|
| + String singleLocationToCssColor(int index) {
|
| + return "background:${toPattern(index)};";
|
| + }
|
| +
|
| + String multiLocationToCssColor(List<int> indices) {
|
| +
|
| + StringBuffer sb = new StringBuffer();
|
| + double delta = 100.0 / (indices.length);
|
| + double position = 0.0;
|
| +
|
| + void addColor(String color) {
|
| + sb.write(', ${color} ${position.toInt()}%');
|
| + position += delta;
|
| + sb.write(', ${color} ${position.toInt()}%');
|
| + }
|
| +
|
| + for (int index in indices) {
|
| + addColor('${toColorCss(index)}');
|
| + }
|
| + return 'background: linear-gradient(to right${sb}); '
|
| + 'background-size: 10px 10px;';
|
| + }
|
| +}
|
| +
|
| +class SingleColorScheme implements CssColorScheme {
|
| + const SingleColorScheme();
|
| +
|
| + bool get showLocationAsSpan => false;
|
| +
|
| + String singleLocationToCssColor(int index) {
|
| + return "background:${toColorCss(index)};";
|
| + }
|
| +
|
| + String multiLocationToCssColor(List<int> indices) {
|
| + StringBuffer sb = new StringBuffer();
|
| + double delta = 100.0 / (indices.length);
|
| + double position = 0.0;
|
| +
|
| + void addColor(String color) {
|
| + sb.write(', ${color} ${position.toInt()}%');
|
| + position += delta;
|
| + sb.write(', ${color} ${position.toInt()}%');
|
| + }
|
| +
|
| + for (int index in indices) {
|
| + addColor('${toColorCss(index)}');
|
| + }
|
| + return 'background: linear-gradient(to bottom${sb}); '
|
| + 'background-size: 10px 3px;';
|
| + }
|
| +}
|
| +
|
| /// Processor that computes the HTML representation of a block of JavaScript
|
| /// code and collects the source locations mapped in the code.
|
| class CodeProcessor {
|
| int lineIndex = 0;
|
| - final String onclick;
|
| + final String name;
|
| int currentJsSourceOffset = 0;
|
| final SourceLocationCollection collection;
|
| final Map<int, List<SourceLocation>> codeLocations = {};
|
| + final CssColorScheme colorScheme;
|
|
|
| - CodeProcessor(this.onclick, this.collection);
|
| + CodeProcessor(this.name, this.collection,
|
| + {this.colorScheme: const PatternCssColorScheme()});
|
|
|
| void addSourceLocation(int targetOffset, SourceLocation sourceLocation) {
|
| codeLocations.putIfAbsent(targetOffset, () => []).add(sourceLocation);
|
| @@ -101,102 +193,275 @@ class CodeProcessor {
|
| }
|
|
|
| String convertToHtml(String text) {
|
| - StringBuffer htmlBuffer = new StringBuffer();
|
| - int offset = 0;
|
| - int lineIndex = 0;
|
| - bool pendingSourceLocationsEnd = false;
|
| - htmlBuffer.write(lineNumber(lineIndex));
|
| - SourceLocation currentLocation;
|
| -
|
| - void endCurrentLocation() {
|
| - if (currentLocation != null) {
|
| - htmlBuffer.write('</a>');
|
| + List<Annotation> annotations = <Annotation>[];
|
| + codeLocations.forEach((int codeOffset, List<SourceLocation> locations) {
|
| + for (SourceLocation location in locations) {
|
| + if (location != null) {
|
| + annotations.add(new Annotation(
|
| + collection.getIndex(location),
|
| + codeOffset,
|
| + location.shortText));
|
| + }
|
| }
|
| - currentLocation = null;
|
| + });
|
| + return convertAnnotatedCodeToHtml(
|
| + text, annotations, colorScheme: colorScheme,
|
| + elementScheme: new HighlightLinkScheme(name),
|
| + windowSize: 3);
|
| + }
|
| +}
|
| +
|
| +class Annotation {
|
| + final id;
|
| + final int codeOffset;
|
| + final String title;
|
| +
|
| + Annotation(this.id, this.codeOffset, this.title);
|
| +}
|
| +
|
| +class ElementScheme {
|
| + const ElementScheme();
|
| +
|
| + String getName(var id, Set ids) => null;
|
| + String getHref(var id, Set ids) => null;
|
| + String onClick(var id, Set ids) => null;
|
| + String onMouseOver(var id, Set ids) => null;
|
| + String onMouseOut(var id, Set ids) => null;
|
| +}
|
| +
|
| +class HighlightLinkScheme implements ElementScheme {
|
| + final String name;
|
| +
|
| + HighlightLinkScheme(this.name);
|
| +
|
| + @override
|
| + String getName(int id, Set<int> indices) {
|
| + return 'js$id';
|
| + }
|
| +
|
| + @override
|
| + String getHref(int id, Set<int> indices) {
|
| + return "#${id}";
|
| + }
|
| +
|
| + @override
|
| + String onClick(int id, Set<int> indices) {
|
| + return "show(\'$name\');";
|
| + }
|
| +
|
| + @override
|
| + String onMouseOut(int id, Set<int> indices) {
|
| + String onmouseover = indices.map((i) => '\'$i\'').join(',');
|
| + return "highlight([${onmouseover}]);";
|
| + }
|
| +
|
| + @override
|
| + String onMouseOver(int id, Set<int> indices) {
|
| + return "highlight([]);";
|
| + }
|
| +}
|
| +
|
| +String convertAnnotatedCodeToHtml(
|
| + String code,
|
| + Iterable<Annotation> annotations,
|
| + {CssColorScheme colorScheme: const SingleColorScheme(),
|
| + ElementScheme elementScheme: const ElementScheme(),
|
| + int windowSize}) {
|
| + StringBuffer htmlBuffer = new StringBuffer();
|
| + List<CodeLine> lines = convertAnnotatedCodeToCodeLines(
|
| + code, annotations,
|
| + colorScheme: colorScheme,
|
| + elementScheme: elementScheme,
|
| + windowSize: windowSize);
|
| + int lineNoWidth;
|
| + if (lines.isNotEmpty) {
|
| + lineNoWidth = '${lines.last.lineNo + 1}'.length;
|
| + }
|
| + for (CodeLine line in lines) {
|
| + line.printHtmlOn(htmlBuffer, lineNoWidth);
|
| + }
|
| + return htmlBuffer.toString();
|
| +}
|
| +
|
| +List<CodeLine> convertAnnotatedCodeToCodeLines(
|
| + String code,
|
| + Iterable<Annotation> annotations,
|
| + {CssColorScheme colorScheme: const SingleColorScheme(),
|
| + ElementScheme elementScheme: const ElementScheme(),
|
| + int windowSize}) {
|
| +
|
| + List<CodeLine> lines = <CodeLine>[];
|
| + CodeLine currentLine;
|
| + int offset = 0;
|
| + int lineIndex = 0;
|
| + int firstLine;
|
| + int lastLine;
|
| + bool pendingSourceLocationsEnd = false;
|
| +
|
| + void write(String code, String html) {
|
| + if (currentLine != null) {
|
| + currentLine.codeBuffer.write(code);
|
| + currentLine.htmlParts.add(html);
|
| }
|
| + }
|
|
|
| - void addSubstring(int until) {
|
| - if (until <= offset) return;
|
| + void startLine() {
|
| + lines.add(currentLine = new CodeLine(lines.length));
|
| + }
|
|
|
| - String substring = text.substring(offset, until);
|
| - offset = until;
|
| - bool first = true;
|
| - for (String line in substring.split('\n')) {
|
| - if (!first) {
|
| - endCurrentLocation();
|
| - htmlBuffer.write('\n');
|
| - lineIndex++;
|
| - htmlBuffer.write(lineNumber(lineIndex));
|
| - }
|
| - htmlBuffer.write(escape(line));
|
| - first = false;
|
| - }
|
| + void endCurrentLocation() {
|
| + if (pendingSourceLocationsEnd) {
|
| + write('', '</a>');
|
| }
|
| + pendingSourceLocationsEnd = false;
|
| + }
|
|
|
| - void insertSourceLocations(List<SourceLocation> lastSourceLocations) {
|
| - endCurrentLocation();
|
| + void addSubstring(int until, {bool isFirst: false, bool isLast: false}) {
|
| + if (until <= offset) return;
|
| + if (offset >= code.length) return;
|
| +
|
| + String substring = code.substring(offset, until);
|
| + offset = until;
|
| + bool first = true;
|
|
|
| - String color;
|
| - int index;
|
| - String title;
|
| - if (lastSourceLocations.length == 1) {
|
| - SourceLocation sourceLocation = lastSourceLocations.single;
|
| - if (sourceLocation != null) {
|
| - index = collection.getIndex(sourceLocation);
|
| - color = "background:${toPattern(index)};";
|
| - title = sourceLocation.shortText;
|
| - currentLocation = sourceLocation;
|
| + if (isLast) {
|
| + lastLine = lineIndex;
|
| + }
|
| + if (isFirst) {
|
| + startLine();
|
| + }
|
| + for (String line in substring.split('\n')) {
|
| + if (!first) {
|
| + endCurrentLocation();
|
| + write('', '\n');
|
| + lineIndex++;
|
| + startLine();
|
| + }
|
| + if (pendingSourceLocationsEnd && !colorScheme.showLocationAsSpan) {
|
| + if (line.isNotEmpty) {
|
| + String before = line.substring(0, 1);
|
| + write(before, escape(before));
|
| + endCurrentLocation();
|
| + String after = line.substring(1);
|
| + write(after, escape(after));
|
| }
|
| } else {
|
| + write(line, escape(line));
|
| + }
|
| + first = false;
|
| + }
|
| + if (isFirst) {
|
| + firstLine = lineIndex;
|
| + }
|
| + }
|
|
|
| - index = collection.getIndex(lastSourceLocations.first);
|
| - StringBuffer sb = new StringBuffer();
|
| - double delta = 100.0 / (lastSourceLocations.length);
|
| - double position = 0.0;
|
| -
|
| - void addColor(String color) {
|
| - sb.write(', ${color} ${position.toInt()}%');
|
| - position += delta;
|
| - sb.write(', ${color} ${position.toInt()}%');
|
| - }
|
| + void insertAnnotations(List<Annotation> annotations) {
|
| + endCurrentLocation();
|
|
|
| - for (SourceLocation sourceLocation in lastSourceLocations) {
|
| - if (sourceLocation == null) continue;
|
| - int colorIndex = collection.getIndex(sourceLocation);
|
| - addColor('${toColorCss(colorIndex)}');
|
| - currentLocation = sourceLocation;
|
| - }
|
| - color = 'background: linear-gradient(to right${sb}); '
|
| - 'background-size: 10px 10px;';
|
| - title = lastSourceLocations.map((l) => l.shortText).join(',');
|
| + String color;
|
| + var id;
|
| + String title;
|
| + if (annotations.length == 1) {
|
| + Annotation annotation = annotations.single;
|
| + if (annotation != null) {
|
| + id = annotation.id;
|
| + color = colorScheme.singleLocationToCssColor(id);
|
| + title = annotation.title;
|
| }
|
| - if (index != null) {
|
| - Set<int> indices =
|
| - lastSourceLocations.map((l) => collection.getIndex(l)).toSet();
|
| - String onmouseover = indices.map((i) => '\'$i\'').join(',');
|
| - htmlBuffer.write(
|
| - '<a name="js$index" href="#${index}" style="$color" title="$title" '
|
| - 'onclick="${onclick}" onmouseover="highlight([${onmouseover}]);"'
|
| - 'onmouseout="highlight([]);">');
|
| - pendingSourceLocationsEnd = true;
|
| + } else {
|
| + id = annotations.first.id;
|
| + List ids = [];
|
| + for (Annotation annotation in annotations) {
|
| + ids.add(annotation.id);
|
| }
|
| - if (lastSourceLocations.last == null) {
|
| - endCurrentLocation();
|
| + color = colorScheme.multiLocationToCssColor(ids);
|
| + title = annotations.map((l) => l.title).join(',');
|
| + }
|
| + if (id != null) {
|
| + Set ids = annotations.map((l) => l.id).toSet();
|
| + String name = elementScheme.getName(id, ids);
|
| + String href = elementScheme.getHref(id, ids);
|
| + String onclick = elementScheme.onClick(id, ids);
|
| + String onmouseover = elementScheme.onMouseOver(id, ids);
|
| + String onmouseout = elementScheme.onMouseOut(id, ids);
|
| + write('', '<a');
|
| + if (href != null) {
|
| + write('', ' href="${href}"');
|
| + }
|
| + if (name != null) {
|
| + write('', ' name="${name}"');
|
| + }
|
| + if (title != null) {
|
| + write('', ' title="${escape(title)}"');
|
| + }
|
| + write('', ' style="${color}"');
|
| + if (onclick != null) {
|
| + write('', ' onclick="${onclick}"');
|
| + }
|
| + if (onmouseover != null) {
|
| + write('', ' onmouseover="${onmouseover}"');
|
| }
|
| + if (onmouseout != null) {
|
| + write('', ' onmouseout="${onmouseout}"');
|
| + }
|
| + write('', '>');
|
| + pendingSourceLocationsEnd = true;
|
| + }
|
| + if (annotations.last == null) {
|
| + endCurrentLocation();
|
| }
|
| + }
|
| +
|
| + Map<int, List<Annotation>> annotationMap = <int, List<Annotation>>{};
|
| + for (Annotation annotation in annotations) {
|
| + annotationMap.putIfAbsent(annotation.codeOffset, () => <Annotation>[])
|
| + .add(annotation);
|
| + }
|
| +
|
| + bool first = true;
|
| + for (int codeOffset in annotationMap.keys.toList()..sort()) {
|
| + List<Annotation> annotationList = annotationMap[codeOffset];
|
| + addSubstring(codeOffset, isFirst: first);
|
| + insertAnnotations(annotationList);
|
| + first = false;
|
| + }
|
| +
|
| + addSubstring(code.length, isFirst: first, isLast: true);
|
| + endCurrentLocation();
|
| +
|
| + int start = 0;
|
| + int end = lines.length - 1;
|
| + if (windowSize != null) {
|
| + start = Math.max(firstLine - windowSize, start);
|
| + end = Math.min(lastLine + windowSize, end);
|
| + }
|
| + return lines.sublist(start, end);
|
| +}
|
| +
|
| +class CodeLine {
|
| + final int lineNo;
|
| + final StringBuffer codeBuffer = new StringBuffer();
|
| + final List<String> htmlParts = <String>[];
|
| + String _code;
|
| +
|
| + CodeLine(this.lineNo);
|
|
|
| - for (int targetOffset in codeLocations.keys.toList()..sort()) {
|
| - List<SourceLocation> sourceLocations = codeLocations[targetOffset];
|
| - addSubstring(targetOffset);
|
| - insertSourceLocations(sourceLocations);
|
| + String get code {
|
| + if (_code == null) {
|
| + _code = codeBuffer.toString();
|
| }
|
| + return _code;
|
| + }
|
|
|
| - addSubstring(text.length);
|
| - endCurrentLocation();
|
| - return htmlBuffer.toString();
|
| + void printHtmlOn(StringBuffer htmlBuffer, [int lineNoWidth]) {
|
| + htmlBuffer.write(lineNumber(lineNo, lineNoWidth));
|
| + for (String part in htmlParts) {
|
| + htmlBuffer.write(part);
|
| + }
|
| }
|
| }
|
|
|
| +
|
| /// Computes the HTML representation for a collection of JavaScript code blocks.
|
| String computeJsHtml(Iterable<SourceMapHtmlInfo> infoList) {
|
|
|
| @@ -240,10 +505,10 @@ SourceMapHtmlInfo createHtmlInfo(SourceLocationCollection collection,
|
| js.Node node = info.node;
|
| String code = info.code;
|
| String name = info.name;
|
| - String onclick = 'show(\'$name\');';
|
| SourceLocationCollection subcollection =
|
| new SourceLocationCollection(collection);
|
| - CodeProcessor codeProcessor = new CodeProcessor(onclick, subcollection);
|
| + CodeProcessor codeProcessor = new CodeProcessor(name, subcollection);
|
| + //print('${info.element}:${info.nodeMap.nodes.length}');
|
| for (js.Node node in info.nodeMap.nodes) {
|
| info.nodeMap[node].forEach(
|
| (int targetOffset, List<SourceLocation> sourceLocations) {
|
| @@ -312,6 +577,11 @@ String computeDartHtmlPart(String name,
|
|
|
| int firstLineIndex;
|
| int lastLineIndex;
|
| + List<int> lineIndices = uriMap.keys.toList()..sort();
|
| + int lineNoWidth;
|
| + if (lineIndices.isNotEmpty) {
|
| + lineNoWidth = '${lineIndices.last + windowSize + 1}'.length;
|
| + }
|
|
|
| void flush() {
|
| if (firstLineIndex != null && lastLineIndex != null) {
|
| @@ -325,7 +595,7 @@ String computeDartHtmlPart(String name,
|
| line < firstLineIndex;
|
| line++) {
|
| if (line >= 0) {
|
| - dartCodeBuffer.write(lineNumber(line));
|
| + dartCodeBuffer.write(lineNumber(line, lineNoWidth));
|
| dartCodeBuffer.write(sourceFile.getLineText(line));
|
| }
|
| }
|
| @@ -334,7 +604,7 @@ String computeDartHtmlPart(String name,
|
| line <= lastLineIndex + windowSize;
|
| line++) {
|
| if (line < sourceFile.lines) {
|
| - dartCodeBuffer.write(lineNumber(line));
|
| + dartCodeBuffer.write(lineNumber(line, lineNoWidth));
|
| dartCodeBuffer.write(sourceFile.getLineText(line));
|
| }
|
| }
|
| @@ -345,7 +615,6 @@ String computeDartHtmlPart(String name,
|
| codeBuffer.clear();
|
| }
|
|
|
| - List<int> lineIndices = uriMap.keys.toList()..sort();
|
| lineIndices.forEach((int lineIndex) {
|
| List<SourceLocation> locations = uriMap[lineIndex];
|
| if (lastLineIndex != null &&
|
| @@ -356,7 +625,7 @@ String computeDartHtmlPart(String name,
|
| firstLineIndex = lineIndex;
|
| } else {
|
| for (int line = lastLineIndex + 1; line < lineIndex; line++) {
|
| - codeBuffer.write(lineNumber(line));
|
| + codeBuffer.write(lineNumber(line, lineNoWidth));
|
| codeBuffer.write(sourceFile.getLineText(line));
|
| }
|
| }
|
| @@ -371,7 +640,7 @@ String computeDartHtmlPart(String name,
|
| end = locations[i + 1].column;
|
| }
|
| if (i == 0) {
|
| - codeBuffer.write(lineNumber(lineIndex));
|
| + codeBuffer.write(lineNumber(lineIndex, lineNoWidth));
|
| codeBuffer.write(line.substring(0, start));
|
| }
|
| codeBuffer.write(
|
|
|