| 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..a6d02bec24852d68244abe8d3efb98fb3444cabb
|
| --- /dev/null
|
| +++ b/tools/dart2js/sourceMapViewer/web/display.dart
|
| @@ -0,0 +1,323 @@
|
| +// 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.
|
| +
|
| +import 'dart:html';
|
| +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', '/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++) {
|
| + 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 DivElement()
|
| + ..children.add(getLineNumberElement(lineNumber)));
|
| + 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..children.add(createSpan(token, previous, lineEntry));
|
| + }
|
| + 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 {
|
| + 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>";
|
| +}
|
|
|