b35b436cf5
Change-Id: Id0257e4d00ca4a7acc759d7f06fa5b64c744894b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/256126 Reviewed-by: Srujan Gaddam <srujzs@google.com> Commit-Queue: Lasse Nielsen <lrn@google.com> Reviewed-by: Michael Thomsen <mit@google.com> Reviewed-by: Nicholas Shahan <nshahan@google.com>
322 lines
10 KiB
Dart
322 lines
10 KiB
Dart
// 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;
|
|
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(jsonDecode(mapFileContent));
|
|
displayMap(mapFileContent);
|
|
targetFileName.text = sourceMap.targetUrl;
|
|
load(targetFileName.text).then((targetFileContent) {
|
|
target = targetFileContent.split('\n');
|
|
displayTargetSource();
|
|
adjustDivHeightsToWindow();
|
|
});
|
|
});
|
|
});
|
|
|
|
sourceFileName.text = "<source not selected>";
|
|
}
|