// 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> targetEntryMap = {}; List highlightedMapEntry = null; List 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 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 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 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 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 = ""; }