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( |