Chromium Code Reviews| Index: tools/dart2js/sourceMapViewer/web/display.dart |
| diff --git a/tools/dart2js/sourceMapViewer/web/display.dart b/tools/dart2js/sourceMapViewer/web/display.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..cdce597a914fc4283c0797acdf4d4ea0bfbd1cd7 |
| --- /dev/null |
| +++ b/tools/dart2js/sourceMapViewer/web/display.dart |
| @@ -0,0 +1,315 @@ |
| +import 'dart:html'; |
|
ricow1
2014/05/14 12:28:08
missing copyright header
zarah
2014/05/14 20:51:03
Done.
|
| +import 'dart:convert'; |
| +import 'dart:async'; |
| +import 'package:source_maps/source_maps.dart'; |
| + |
| +Element targetFileName = querySelector("#target_filename"); |
| +Element sourceFileName = querySelector("#source_filename"); |
| +DivElement generatedOutput = querySelector("#generated_output"); |
| +DivElement selectedSource = querySelector("#selected_source"); |
| +DivElement selectedOutputSpan = querySelector("#current_span"); |
| +DivElement decodedMap = querySelector("#decoded_map"); |
| +DivElement originalMap = querySelector("#original_map"); |
| + |
| +Map<TargetEntry, List<SpanElement>> targetEntryMap = {}; |
| +List<SpanElement> highlightedMapEntry = null; |
| +List<String> target; |
| +SingleMapping sourceMap; |
| + |
| +void adjustDivHeightsToWindow() { |
| + generatedOutput.style.height = "${window.innerHeight / 3 - 50}px"; |
| + selectedSource.style.height = "${window.innerHeight / 3 - 50}px"; |
| + decodedMap.style.height = "${window.innerHeight / 3 - 50}px"; |
| + originalMap.style.height = "${window.innerHeight / 3 - 50}px"; |
| +} |
| + |
| +Future getMap() { |
| + Completer c = new Completer(); |
| + HttpRequest httpRequest = new HttpRequest(); |
| + httpRequest |
| + ..open('GET', 'http://127.0.0.1:9223/map') |
| + ..onLoadEnd.listen((_) => c.complete(httpRequest.responseText)) |
| + ..send(''); |
| + return c.future; |
| +} |
| + |
| +Future fetchFile(String path) { |
| + Completer c = new Completer(); |
| + HttpRequest httpRequest = new HttpRequest(); |
| + sourceFileName.text = path; |
| + httpRequest |
| + ..open('GET', path) |
| + ..onLoadEnd.listen((_) => c.complete(httpRequest.responseText)) |
| + ..send(''); |
| + return c.future; |
| +} |
| + |
| +displaySource(String filename, List<String> source, TargetEntry entry) { |
| + int line = entry.sourceLine; |
| + int column = entry.sourceColumn; |
| + int nameId = entry.sourceNameId; |
| + String id = nameId == null ? null : sourceMap.names[nameId]; |
| + selectedSource.children.clear(); |
| + SpanElement marker = new SpanElement() |
| + ..className = "marker" |
| + ..appendText("*"); |
| + for (int pos = 0; pos < source.length; pos++) { |
|
ricow1
2014/05/14 12:28:08
for (var l in source)
zarah
2014/05/14 20:51:03
The variable pos is needed below so this would not
|
| + String l = source[pos]; |
| + if (pos != line) { |
| + selectedSource.children.add(l.isEmpty ? new BRElement() : new DivElement() |
| + ..appendText(l)); |
| + } else { |
| + selectedSource.children.add(new DivElement() |
| + ..appendText(l.substring(0, column)) |
| + ..children.add(marker) |
| + ..appendText(l.substring(column))); |
| + } |
| + } |
| + sourceFileName.text = filename; |
| + marker.scrollIntoView(); |
| +} |
| + |
| +void highlightSelectedSpan(TargetEntry entry, TargetLineEntry lineEntry) { |
| + selectedOutputSpan.children.clear(); |
| + String spanEndCol; |
| + TargetEntry spanEnd; |
| + bool nextEntryIsSpanEnd = false; |
| + for (TargetEntry e in lineEntry.entries) { |
| + if (nextEntryIsSpanEnd) { |
| + spanEnd = e; |
| + break; |
| + } |
| + if (e == entry) { |
| + nextEntryIsSpanEnd = true; |
| + } |
| + } |
| + if (spanEnd == null) { |
| + spanEndCol = '${target[lineEntry.line].length} (EOL).'; |
| + } else { |
| + spanEndCol = '${spanEnd.column}.'; |
| + } |
| + |
| + String targetSpan = |
| + 'Target: Line ${lineEntry.line} Col. ${entry.column} - $spanEndCol'; |
| + |
| + if (entry.sourceUrlId == null) { |
| + targetSpan += ' Source: unknown'; |
| + selectedOutputSpan.children.add(getTextElement(targetSpan)); |
| + return; |
| + } |
| + |
| + String source = sourceMap.urls[entry.sourceUrlId]; |
| + String sourceName = source.substring(source.lastIndexOf('/') + 1); |
| + String sourcePoint = |
| + 'Source: Line ${entry.sourceLine} Col. ${entry.sourceColumn}'; |
| + sourcePoint += |
| + entry.sourceNameId == null ? '' |
| + : ' (${sourceMap.names[entry.sourceNameId]})'; |
| + sourcePoint += ' in $sourceName'; |
| + selectedOutputSpan.children.add(getTextElement(targetSpan)); |
| + selectedOutputSpan.children.add(new BRElement()); |
| + selectedOutputSpan.children.add(getTextElement(sourcePoint)); |
| + |
| + if (highlightedMapEntry != null) { |
| + highlightedMapEntry[0].style.background = 'white'; |
| + highlightedMapEntry[1].style.background = 'white'; |
| + } |
| + |
| + String highlightColor = "#99ff99"; |
| + highlightedMapEntry = targetEntryMap[entry]; |
| + highlightedMapEntry[0] |
| + ..scrollIntoView() |
| + ..style.backgroundColor = highlightColor; |
| + highlightedMapEntry[1] |
| + ..scrollIntoView() |
| + ..style.backgroundColor = highlightColor; |
| + highlightedMapEntry[1].onMouseOver.listen((e) { |
| + selectedOutputSpan.style.zIndex = "2"; |
| + selectedOutputSpan.style.visibility = "visible"; |
| + selectedOutputSpan.style.top = "${decodedMap.offsetTo(document.body).y + |
| + decodedMap.clientHeight - 20}px"; |
| + selectedOutputSpan.style.left = "${decodedMap.offsetTo(document.body).x}px"; |
| + selectedOutputSpan.style.width= "${decodedMap.clientWidth}px"; |
| + }); |
| + |
| + highlightedMapEntry[1].onMouseOut.listen( (e) { |
| + selectedOutputSpan.style.visibility = "hidden"; |
| + }); |
| + |
| + adjustDivHeightsToWindow(); |
| +} |
| + |
| +void loadSource(TargetEntry entry) { |
| + if (entry.sourceUrlId == null) { |
| + return; |
| + } |
| + |
| + String source = sourceMap.urls[entry.sourceUrlId]; |
| + fetchFile(new Uri(path: "/file", |
| + queryParameters: {"path": source}).toString()).then((text) |
| + => displaySource(source, text.split("\n"), entry)); |
| + selectedSource.text = "loading"; |
| +} |
| + |
| +SpanElement createSpan(String content, TargetEntry entry, |
| + TargetLineEntry lineEntry) { |
| + return new SpanElement() |
| + ..addEventListener('click', (e) { |
| + loadSource(entry); |
| + highlightSelectedSpan(entry, lineEntry); |
| + }, false) |
| + ..className = "range${entry.sourceUrlId % 4}" |
| + ..appendText(content); |
| +} |
| + |
| +Element getLineNumberElement(int line) { |
| + SpanElement result = new SpanElement(); |
| + result.style.fontFamily = "Courier"; |
| + result.style.fontSize = "10pt"; |
| + result.appendText("${line} "); |
| + return result; |
| +} |
| + |
| +Element getTextElement(String text) { |
| + SpanElement result = new SpanElement(); |
| + result.text = text; |
| + return result; |
| +} |
| + |
| +addTargetLine(int lineNumber, String content, TargetLineEntry lineEntry) { |
| + if (content.isEmpty) { |
| + generatedOutput.children.add(new BRElement()); |
|
Johnni Winther
2014/05/14 12:00:38
Replace `new BRElement()` by `new DivElement()..ch
zarah
2014/05/14 20:51:03
Done.
|
| + return; |
| + } |
| + if (lineEntry == null) { |
| + generatedOutput.children.add(new DivElement() |
| + ..children.add(getLineNumberElement(lineNumber)) |
| + ..children.add(getTextElement(content))); |
| + return; |
| + } |
| + DivElement div = new DivElement(); |
| + div.children.add(getLineNumberElement(lineNumber)); |
| + |
| + int pos = 0; |
| + TargetEntry previous = null; |
| + for (TargetEntry next in lineEntry.entries) { |
| + if (previous == null) { |
| + if (pos < next.column) { |
| + div.appendText(content.substring(pos, next.column)); |
| + } |
| + if (content.length == next.column) { |
| + div.children.add(createSpan(" ", next, lineEntry)); |
| + } |
| + } else { |
| + if (next.column <= content.length) { |
| + String token = content.substring(pos, next.column); |
| + div.children.add(createSpan(token, previous, lineEntry)); |
| + } |
| + if (content.length == next.column) { |
| + div.children.add(createSpan(" ", next, lineEntry)); |
| + } |
| + } |
| + pos = next.column; |
| + previous = next; |
| + } |
| + String token = content.substring(pos); |
| + if (previous == null) { |
| + div.appendText(token); |
| + } else { |
| + div..appendText(" ")..children.add(createSpan(token, previous, lineEntry)); |
|
Johnni Winther
2014/05/14 12:00:38
Remove `..appendText(" ")`.
zarah
2014/05/14 20:51:03
Done.
|
| + } |
| + generatedOutput.children.add(div); |
| +} |
| + |
| +// Display the target source in the HTML. |
| +void displayTargetSource() { |
| + List<TargetLineEntry> targetLines = sourceMap.lines; |
| + int linesIndex = 0; |
| + for (int line = 0; line < target.length; line++) { |
| + TargetLineEntry entry = null; |
| + if (linesIndex < targetLines.length |
| + && targetLines[linesIndex].line == line) { |
| + entry = targetLines[linesIndex]; |
| + linesIndex++; |
| + } |
| + if (entry != null) addTargetLine(line, target[line], entry); else |
|
Johnni Winther
2014/05/14 12:00:38
Add {} to the branches.
zarah
2014/05/14 20:51:03
Done.
|
| + addTargetLine(line, target[line], null); |
| + } |
| +} |
| + |
| +String getMappedData(String mapFileContent) { |
| + // Source map contains mapping information in this format: |
| + // "mappings": "A;A,yC;" |
| + List<String> mapEntry = mapFileContent.split('mappings'); |
| + return mapEntry[mapEntry.length-1].split('"')[2]; |
| +} |
| + |
| +SpanElement createMapSpan(String segment) { |
| + return new SpanElement()..text = segment; |
| +} |
| + |
| +SpanElement createDecodedMapSpan(TargetEntry entry) { |
| + return new SpanElement()..text = '(${entry.column}, ${entry.sourceUrlId},' |
| + ' ${entry.sourceLine},' |
| + ' ${entry.sourceColumn})'; |
| +} |
| + |
| +displayMap(String mapFileContent) { |
| + String mappedData = getMappedData(mapFileContent); |
| + int sourceMapLine = 0; |
| + for (String group in mappedData.split(';')) { |
| + if (group.length == 0) continue; |
| + |
| + List<String> segments = []; |
| + if (!group.contains(',')) { |
| + segments.add(group); |
| + } else { |
| + segments = group.split(','); |
| + } |
| + |
| + TargetLineEntry targetLineEntry = sourceMap.lines[sourceMapLine]; |
| + decodedMap.children.add(getLineNumberElement(targetLineEntry.line)); |
| + originalMap.children.add(getLineNumberElement(targetLineEntry.line)); |
| + bool first = true; |
| + int entryNumber = 0; |
| + for (String segment in segments) { |
| + TargetEntry entry = targetLineEntry.entries[entryNumber]; |
| + SpanElement orignalMapSpan = createMapSpan(segment); |
| + SpanElement decodedMapSpan = createDecodedMapSpan(entry); |
| + if (first) { |
| + first = false; |
| + } else { |
| + originalMap.children.add(getTextElement(', ')); |
| + decodedMap.children.add(getTextElement(', ')); |
| + } |
| + originalMap.children.add(orignalMapSpan); |
| + decodedMap.children.add(decodedMapSpan); |
| + ++entryNumber; |
| + targetEntryMap.putIfAbsent(entry, () => [orignalMapSpan, decodedMapSpan]); |
| + } |
| + originalMap.children.add(new BRElement()); |
| + decodedMap.children.add(new BRElement()); |
| + ++sourceMapLine; |
| + } |
| +} |
| + |
| +void main() { |
| + Future load(String q) => fetchFile(new Uri(path: "/file", queryParameters: { |
| + "path": q |
| + }).toString()); |
| + |
| + getMap().then((mapFileName) { |
| + load(mapFileName).then((mapFileContent) { |
| + sourceMap = new SingleMapping.fromJson(JSON.decode(mapFileContent)); |
| + displayMap(mapFileContent); |
| + targetFileName.text = sourceMap.targetUrl; |
| + load(targetFileName.text).then((targetFileContent) { |
| + target = targetFileContent.split('\n'); |
| + displayTargetSource(); |
| + adjustDivHeightsToWindow(); |
| + }); |
| + }); |
| + }); |
| + |
| + sourceFileName.text = "<source not selected>"; |
| +} |