OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 import 'dart:html'; |
| 6 import 'dart:convert'; |
| 7 import 'dart:async'; |
| 8 import 'package:source_maps/source_maps.dart'; |
| 9 |
| 10 Element targetFileName = querySelector("#target_filename"); |
| 11 Element sourceFileName = querySelector("#source_filename"); |
| 12 DivElement generatedOutput = querySelector("#generated_output"); |
| 13 DivElement selectedSource = querySelector("#selected_source"); |
| 14 DivElement selectedOutputSpan = querySelector("#current_span"); |
| 15 DivElement decodedMap = querySelector("#decoded_map"); |
| 16 DivElement originalMap = querySelector("#original_map"); |
| 17 |
| 18 Map<TargetEntry, List<SpanElement>> targetEntryMap = {}; |
| 19 List<SpanElement> highlightedMapEntry = null; |
| 20 List<String> target; |
| 21 SingleMapping sourceMap; |
| 22 |
| 23 void adjustDivHeightsToWindow() { |
| 24 generatedOutput.style.height = "${window.innerHeight / 3 - 50}px"; |
| 25 selectedSource.style.height = "${window.innerHeight / 3 - 50}px"; |
| 26 decodedMap.style.height = "${window.innerHeight / 3 - 50}px"; |
| 27 originalMap.style.height = "${window.innerHeight / 3 - 50}px"; |
| 28 } |
| 29 |
| 30 Future getMap() { |
| 31 Completer c = new Completer(); |
| 32 HttpRequest httpRequest = new HttpRequest(); |
| 33 httpRequest |
| 34 ..open('GET', '/map') |
| 35 ..onLoadEnd.listen((_) => c.complete(httpRequest.responseText)) |
| 36 ..send(''); |
| 37 return c.future; |
| 38 } |
| 39 |
| 40 Future fetchFile(String path) { |
| 41 Completer c = new Completer(); |
| 42 HttpRequest httpRequest = new HttpRequest(); |
| 43 sourceFileName.text = path; |
| 44 httpRequest |
| 45 ..open('GET', path) |
| 46 ..onLoadEnd.listen((_) => c.complete(httpRequest.responseText)) |
| 47 ..send(''); |
| 48 return c.future; |
| 49 } |
| 50 |
| 51 displaySource(String filename, List<String> source, TargetEntry entry) { |
| 52 int line = entry.sourceLine; |
| 53 int column = entry.sourceColumn; |
| 54 int nameId = entry.sourceNameId; |
| 55 String id = nameId == null ? null : sourceMap.names[nameId]; |
| 56 selectedSource.children.clear(); |
| 57 SpanElement marker = new SpanElement() |
| 58 ..className = "marker" |
| 59 ..appendText("*"); |
| 60 for (int pos = 0; pos < source.length; pos++) { |
| 61 String l = source[pos]; |
| 62 if (pos != line) { |
| 63 selectedSource.children.add(l.isEmpty ? new BRElement() : new DivElement() |
| 64 ..appendText(l)); |
| 65 } else { |
| 66 selectedSource.children.add(new DivElement() |
| 67 ..appendText(l.substring(0, column)) |
| 68 ..children.add(marker) |
| 69 ..appendText(l.substring(column))); |
| 70 } |
| 71 } |
| 72 sourceFileName.text = filename; |
| 73 marker.scrollIntoView(); |
| 74 } |
| 75 |
| 76 void highlightSelectedSpan(TargetEntry entry, TargetLineEntry lineEntry) { |
| 77 selectedOutputSpan.children.clear(); |
| 78 String spanEndCol; |
| 79 TargetEntry spanEnd; |
| 80 bool nextEntryIsSpanEnd = false; |
| 81 for (TargetEntry e in lineEntry.entries) { |
| 82 if (nextEntryIsSpanEnd) { |
| 83 spanEnd = e; |
| 84 break; |
| 85 } |
| 86 if (e == entry) { |
| 87 nextEntryIsSpanEnd = true; |
| 88 } |
| 89 } |
| 90 if (spanEnd == null) { |
| 91 spanEndCol = '${target[lineEntry.line].length} (EOL).'; |
| 92 } else { |
| 93 spanEndCol = '${spanEnd.column}.'; |
| 94 } |
| 95 |
| 96 String targetSpan = |
| 97 'Target: Line ${lineEntry.line} Col. ${entry.column} - $spanEndCol'; |
| 98 |
| 99 if (entry.sourceUrlId == null) { |
| 100 targetSpan += ' Source: unknown'; |
| 101 selectedOutputSpan.children.add(getTextElement(targetSpan)); |
| 102 return; |
| 103 } |
| 104 |
| 105 String source = sourceMap.urls[entry.sourceUrlId]; |
| 106 String sourceName = source.substring(source.lastIndexOf('/') + 1); |
| 107 String sourcePoint = |
| 108 'Source: Line ${entry.sourceLine} Col. ${entry.sourceColumn}'; |
| 109 sourcePoint += |
| 110 entry.sourceNameId == null ? '' |
| 111 : ' (${sourceMap.names[entry.sourceNameId]})'; |
| 112 sourcePoint += ' in $sourceName'; |
| 113 selectedOutputSpan.children.add(getTextElement(targetSpan)); |
| 114 selectedOutputSpan.children.add(new BRElement()); |
| 115 selectedOutputSpan.children.add(getTextElement(sourcePoint)); |
| 116 |
| 117 if (highlightedMapEntry != null) { |
| 118 highlightedMapEntry[0].style.background = 'white'; |
| 119 highlightedMapEntry[1].style.background = 'white'; |
| 120 } |
| 121 |
| 122 String highlightColor = "#99ff99"; |
| 123 highlightedMapEntry = targetEntryMap[entry]; |
| 124 highlightedMapEntry[0] |
| 125 ..scrollIntoView() |
| 126 ..style.backgroundColor = highlightColor; |
| 127 highlightedMapEntry[1] |
| 128 ..scrollIntoView() |
| 129 ..style.backgroundColor = highlightColor; |
| 130 highlightedMapEntry[1].onMouseOver.listen((e) { |
| 131 selectedOutputSpan.style.zIndex = "2"; |
| 132 selectedOutputSpan.style.visibility = "visible"; |
| 133 selectedOutputSpan.style.top = "${decodedMap.offsetTo(document.body).y + |
| 134 decodedMap.clientHeight - 20}px"; |
| 135 selectedOutputSpan.style.left = "${decodedMap.offsetTo(document.body).x}px"; |
| 136 selectedOutputSpan.style.width= "${decodedMap.clientWidth}px"; |
| 137 }); |
| 138 |
| 139 highlightedMapEntry[1].onMouseOut.listen( (e) { |
| 140 selectedOutputSpan.style.visibility = "hidden"; |
| 141 }); |
| 142 |
| 143 adjustDivHeightsToWindow(); |
| 144 } |
| 145 |
| 146 void loadSource(TargetEntry entry) { |
| 147 if (entry.sourceUrlId == null) { |
| 148 return; |
| 149 } |
| 150 |
| 151 String source = sourceMap.urls[entry.sourceUrlId]; |
| 152 fetchFile(new Uri(path: "/file", |
| 153 queryParameters: {"path": source}).toString()).then((text) |
| 154 => displaySource(source, text.split("\n"), entry)); |
| 155 selectedSource.text = "loading"; |
| 156 } |
| 157 |
| 158 SpanElement createSpan(String content, TargetEntry entry, |
| 159 TargetLineEntry lineEntry) { |
| 160 return new SpanElement() |
| 161 ..addEventListener('click', (e) { |
| 162 loadSource(entry); |
| 163 highlightSelectedSpan(entry, lineEntry); |
| 164 }, false) |
| 165 ..className = "range${entry.sourceUrlId % 4}" |
| 166 ..appendText(content); |
| 167 } |
| 168 |
| 169 Element getLineNumberElement(int line) { |
| 170 SpanElement result = new SpanElement(); |
| 171 result.style.fontFamily = "Courier"; |
| 172 result.style.fontSize = "10pt"; |
| 173 result.appendText("${line} "); |
| 174 return result; |
| 175 } |
| 176 |
| 177 Element getTextElement(String text) { |
| 178 SpanElement result = new SpanElement(); |
| 179 result.text = text; |
| 180 return result; |
| 181 } |
| 182 |
| 183 addTargetLine(int lineNumber, String content, TargetLineEntry lineEntry) { |
| 184 if (content.isEmpty) { |
| 185 generatedOutput.children.add(new DivElement() |
| 186 ..children.add(getLineNumberElement(lineNumber))); |
| 187 return; |
| 188 } |
| 189 if (lineEntry == null) { |
| 190 generatedOutput.children.add(new DivElement() |
| 191 ..children.add(getLineNumberElement(lineNumber)) |
| 192 ..children.add(getTextElement(content))); |
| 193 return; |
| 194 } |
| 195 DivElement div = new DivElement(); |
| 196 div.children.add(getLineNumberElement(lineNumber)); |
| 197 |
| 198 int pos = 0; |
| 199 TargetEntry previous = null; |
| 200 for (TargetEntry next in lineEntry.entries) { |
| 201 if (previous == null) { |
| 202 if (pos < next.column) { |
| 203 div.appendText(content.substring(pos, next.column)); |
| 204 } |
| 205 if (content.length == next.column) { |
| 206 div.children.add(createSpan(" ", next, lineEntry)); |
| 207 } |
| 208 } else { |
| 209 if (next.column <= content.length) { |
| 210 String token = content.substring(pos, next.column); |
| 211 div.children.add(createSpan(token, previous, lineEntry)); |
| 212 } |
| 213 if (content.length == next.column) { |
| 214 div.children.add(createSpan(" ", next, lineEntry)); |
| 215 } |
| 216 } |
| 217 pos = next.column; |
| 218 previous = next; |
| 219 } |
| 220 String token = content.substring(pos); |
| 221 if (previous == null) { |
| 222 div.appendText(token); |
| 223 } else { |
| 224 div..children.add(createSpan(token, previous, lineEntry)); |
| 225 } |
| 226 generatedOutput.children.add(div); |
| 227 } |
| 228 |
| 229 // Display the target source in the HTML. |
| 230 void displayTargetSource() { |
| 231 List<TargetLineEntry> targetLines = sourceMap.lines; |
| 232 int linesIndex = 0; |
| 233 for (int line = 0; line < target.length; line++) { |
| 234 TargetLineEntry entry = null; |
| 235 if (linesIndex < targetLines.length |
| 236 && targetLines[linesIndex].line == line) { |
| 237 entry = targetLines[linesIndex]; |
| 238 linesIndex++; |
| 239 } |
| 240 if (entry != null) { |
| 241 addTargetLine(line, target[line], entry); |
| 242 } else { |
| 243 addTargetLine(line, target[line], null); |
| 244 } |
| 245 } |
| 246 } |
| 247 |
| 248 String getMappedData(String mapFileContent) { |
| 249 // Source map contains mapping information in this format: |
| 250 // "mappings": "A;A,yC;" |
| 251 List<String> mapEntry = mapFileContent.split('mappings'); |
| 252 return mapEntry[mapEntry.length-1].split('"')[2]; |
| 253 } |
| 254 |
| 255 SpanElement createMapSpan(String segment) { |
| 256 return new SpanElement()..text = segment; |
| 257 } |
| 258 |
| 259 SpanElement createDecodedMapSpan(TargetEntry entry) { |
| 260 return new SpanElement()..text = '(${entry.column}, ${entry.sourceUrlId},' |
| 261 ' ${entry.sourceLine},' |
| 262 ' ${entry.sourceColumn})'; |
| 263 } |
| 264 |
| 265 displayMap(String mapFileContent) { |
| 266 String mappedData = getMappedData(mapFileContent); |
| 267 int sourceMapLine = 0; |
| 268 for (String group in mappedData.split(';')) { |
| 269 if (group.length == 0) continue; |
| 270 |
| 271 List<String> segments = []; |
| 272 if (!group.contains(',')) { |
| 273 segments.add(group); |
| 274 } else { |
| 275 segments = group.split(','); |
| 276 } |
| 277 |
| 278 TargetLineEntry targetLineEntry = sourceMap.lines[sourceMapLine]; |
| 279 decodedMap.children.add(getLineNumberElement(targetLineEntry.line)); |
| 280 originalMap.children.add(getLineNumberElement(targetLineEntry.line)); |
| 281 bool first = true; |
| 282 int entryNumber = 0; |
| 283 for (String segment in segments) { |
| 284 TargetEntry entry = targetLineEntry.entries[entryNumber]; |
| 285 SpanElement orignalMapSpan = createMapSpan(segment); |
| 286 SpanElement decodedMapSpan = createDecodedMapSpan(entry); |
| 287 if (first) { |
| 288 first = false; |
| 289 } else { |
| 290 originalMap.children.add(getTextElement(', ')); |
| 291 decodedMap.children.add(getTextElement(', ')); |
| 292 } |
| 293 originalMap.children.add(orignalMapSpan); |
| 294 decodedMap.children.add(decodedMapSpan); |
| 295 ++entryNumber; |
| 296 targetEntryMap.putIfAbsent(entry, () => [orignalMapSpan, decodedMapSpan]); |
| 297 } |
| 298 originalMap.children.add(new BRElement()); |
| 299 decodedMap.children.add(new BRElement()); |
| 300 ++sourceMapLine; |
| 301 } |
| 302 } |
| 303 |
| 304 void main() { |
| 305 Future load(String q) => fetchFile(new Uri(path: "/file", queryParameters: { |
| 306 "path": q |
| 307 }).toString()); |
| 308 |
| 309 getMap().then((mapFileName) { |
| 310 load(mapFileName).then((mapFileContent) { |
| 311 sourceMap = new SingleMapping.fromJson(JSON.decode(mapFileContent)); |
| 312 displayMap(mapFileContent); |
| 313 targetFileName.text = sourceMap.targetUrl; |
| 314 load(targetFileName.text).then((targetFileContent) { |
| 315 target = targetFileContent.split('\n'); |
| 316 displayTargetSource(); |
| 317 adjustDivHeightsToWindow(); |
| 318 }); |
| 319 }); |
| 320 }); |
| 321 |
| 322 sourceFileName.text = "<source not selected>"; |
| 323 } |
OLD | NEW |