| // 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>"; |
| } |