| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /// Helper for creating HTML visualization of the source map information | 5 /// Helper for creating HTML visualization of the source map information |
| 6 /// generated by a [SourceMapProcessor]. | 6 /// generated by a [SourceMapProcessor]. |
| 7 | 7 |
| 8 library sourcemap.html.helper; | 8 library sourcemap.html.helper; |
| 9 | 9 |
| 10 import 'dart:convert'; | 10 import 'dart:convert'; |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 47 /// Use gradient on spans to visually identify consecutive spans mapped to the | 47 /// Use gradient on spans to visually identify consecutive spans mapped to the |
| 48 /// same source location. | 48 /// same source location. |
| 49 HSV startColor = toColor(index); | 49 HSV startColor = toColor(index); |
| 50 HSV endColor = new HSV(startColor.h, startColor.s + 0.4, startColor.v - 0.2); | 50 HSV endColor = new HSV(startColor.h, startColor.s + 0.4, startColor.v - 0.2); |
| 51 return 'linear-gradient(to right, ${startColor.toCss}, ${endColor.toCss})'; | 51 return 'linear-gradient(to right, ${startColor.toCss}, ${endColor.toCss})'; |
| 52 } | 52 } |
| 53 | 53 |
| 54 /// Return the html for the [index] line number. If [width] is provided, shorter | 54 /// Return the html for the [index] line number. If [width] is provided, shorter |
| 55 /// line numbers will be prefixed with spaces to match the width. | 55 /// line numbers will be prefixed with spaces to match the width. |
| 56 String lineNumber(int index, | 56 String lineNumber(int index, |
| 57 {int width, | 57 {int width, bool useNbsp: false, String className}) { |
| 58 bool useNbsp: false, | |
| 59 String className}) { | |
| 60 if (className == null) { | 58 if (className == null) { |
| 61 className = 'lineNumber'; | 59 className = 'lineNumber'; |
| 62 } | 60 } |
| 63 String text = '${index + 1}'; | 61 String text = '${index + 1}'; |
| 64 String padding = useNbsp ? ' ' : ' '; | 62 String padding = useNbsp ? ' ' : ' '; |
| 65 if (width != null && text.length < width) { | 63 if (width != null && text.length < width) { |
| 66 text = (padding * (width - text.length)) + text; | 64 text = (padding * (width - text.length)) + text; |
| 67 } | 65 } |
| 68 return '<span class="$className">$text$padding</span>'; | 66 return '<span class="$className">$text$padding</span>'; |
| 69 } | 67 } |
| 70 | 68 |
| 71 /// Return the html escaped [text]. | 69 /// Return the html escaped [text]. |
| 72 String escape(String text) { | 70 String escape(String text) { |
| 73 return const HtmlEscape().convert(text); | 71 return const HtmlEscape().convert(text); |
| 74 } | 72 } |
| 75 | 73 |
| 76 /// Information needed to generate HTML for a single [SourceMapInfo]. | 74 /// Information needed to generate HTML for a single [SourceMapInfo]. |
| 77 class SourceMapHtmlInfo { | 75 class SourceMapHtmlInfo { |
| 78 final SourceMapInfo sourceMapInfo; | 76 final SourceMapInfo sourceMapInfo; |
| 79 final CodeProcessor codeProcessor; | 77 final CodeProcessor codeProcessor; |
| 80 final SourceLocationCollection sourceLocationCollection; | 78 final SourceLocationCollection sourceLocationCollection; |
| 81 | 79 |
| 82 SourceMapHtmlInfo(this.sourceMapInfo, | 80 SourceMapHtmlInfo( |
| 83 this.codeProcessor, | 81 this.sourceMapInfo, this.codeProcessor, this.sourceLocationCollection); |
| 84 this.sourceLocationCollection); | |
| 85 | 82 |
| 86 String toString() { | 83 String toString() { |
| 87 return sourceMapInfo.toString(); | 84 return sourceMapInfo.toString(); |
| 88 } | 85 } |
| 89 } | 86 } |
| 90 | 87 |
| 91 /// A collection of source locations. | 88 /// A collection of source locations. |
| 92 /// | 89 /// |
| 93 /// Used to index source locations for visualization and linking. | 90 /// Used to index source locations for visualization and linking. |
| 94 class SourceLocationCollection { | 91 class SourceLocationCollection { |
| (...skipping 24 matching lines...) Expand all Loading... |
| 119 bool get showLocationAsSpan; | 116 bool get showLocationAsSpan; |
| 120 } | 117 } |
| 121 | 118 |
| 122 class CustomColorScheme implements CssColorScheme { | 119 class CustomColorScheme implements CssColorScheme { |
| 123 final bool showLocationAsSpan; | 120 final bool showLocationAsSpan; |
| 124 final Function single; | 121 final Function single; |
| 125 final Function multi; | 122 final Function multi; |
| 126 | 123 |
| 127 CustomColorScheme( | 124 CustomColorScheme( |
| 128 {this.showLocationAsSpan: false, | 125 {this.showLocationAsSpan: false, |
| 129 String this.single(var id), | 126 String this.single(var id), |
| 130 String this.multi(List ids)}); | 127 String this.multi(List ids)}); |
| 131 | 128 |
| 132 String singleLocationToCssColor(var id) => single != null ? single(id) : null; | 129 String singleLocationToCssColor(var id) => single != null ? single(id) : null; |
| 133 | 130 |
| 134 String multiLocationToCssColor(List ids) => multi != null ? multi(ids) : null; | 131 String multiLocationToCssColor(List ids) => multi != null ? multi(ids) : null; |
| 135 } | 132 } |
| 136 | 133 |
| 137 class PatternCssColorScheme implements CssColorScheme { | 134 class PatternCssColorScheme implements CssColorScheme { |
| 138 const PatternCssColorScheme(); | 135 const PatternCssColorScheme(); |
| 139 | 136 |
| 140 bool get showLocationAsSpan => true; | 137 bool get showLocationAsSpan => true; |
| 141 | 138 |
| 142 String singleLocationToCssColor(int index) { | 139 String singleLocationToCssColor(int index) { |
| 143 return "background:${toPattern(index)};"; | 140 return "background:${toPattern(index)};"; |
| 144 } | 141 } |
| 145 | 142 |
| 146 String multiLocationToCssColor(List<int> indices) { | 143 String multiLocationToCssColor(List<int> indices) { |
| 147 | |
| 148 StringBuffer sb = new StringBuffer(); | 144 StringBuffer sb = new StringBuffer(); |
| 149 double delta = 100.0 / (indices.length); | 145 double delta = 100.0 / (indices.length); |
| 150 double position = 0.0; | 146 double position = 0.0; |
| 151 | 147 |
| 152 void addColor(String color) { | 148 void addColor(String color) { |
| 153 sb.write(', ${color} ${position.toInt()}%'); | 149 sb.write(', ${color} ${position.toInt()}%'); |
| 154 position += delta; | 150 position += delta; |
| 155 sb.write(', ${color} ${position.toInt()}%'); | 151 sb.write(', ${color} ${position.toInt()}%'); |
| 156 } | 152 } |
| 157 | 153 |
| 158 for (int index in indices) { | 154 for (int index in indices) { |
| 159 addColor('${toColorCss(index)}'); | 155 addColor('${toColorCss(index)}'); |
| 160 } | 156 } |
| 161 return 'background: linear-gradient(to right${sb}); ' | 157 return 'background: linear-gradient(to right${sb}); ' |
| 162 'background-size: 10px 10px;'; | 158 'background-size: 10px 10px;'; |
| 163 } | 159 } |
| 164 } | 160 } |
| 165 | 161 |
| 166 class SingleColorScheme implements CssColorScheme { | 162 class SingleColorScheme implements CssColorScheme { |
| 167 const SingleColorScheme(); | 163 const SingleColorScheme(); |
| 168 | 164 |
| 169 bool get showLocationAsSpan => false; | 165 bool get showLocationAsSpan => false; |
| 170 | 166 |
| 171 String singleLocationToCssColor(int index) { | 167 String singleLocationToCssColor(int index) { |
| 172 return "background:${toColorCss(index)};"; | 168 return "background:${toColorCss(index)};"; |
| 173 } | 169 } |
| 174 | 170 |
| 175 String multiLocationToCssColor(List<int> indices) { | 171 String multiLocationToCssColor(List<int> indices) { |
| 176 StringBuffer sb = new StringBuffer(); | 172 StringBuffer sb = new StringBuffer(); |
| 177 double delta = 100.0 / (indices.length); | 173 double delta = 100.0 / (indices.length); |
| 178 double position = 0.0; | 174 double position = 0.0; |
| 179 | 175 |
| 180 void addColor(String color) { | 176 void addColor(String color) { |
| 181 sb.write(', ${color} ${position.toInt()}%'); | 177 sb.write(', ${color} ${position.toInt()}%'); |
| 182 position += delta; | 178 position += delta; |
| 183 sb.write(', ${color} ${position.toInt()}%'); | 179 sb.write(', ${color} ${position.toInt()}%'); |
| 184 } | 180 } |
| 185 | 181 |
| 186 for (int index in indices) { | 182 for (int index in indices) { |
| 187 addColor('${toColorCss(index)}'); | 183 addColor('${toColorCss(index)}'); |
| 188 } | 184 } |
| 189 return 'background: linear-gradient(to bottom${sb}); ' | 185 return 'background: linear-gradient(to bottom${sb}); ' |
| 190 'background-size: 10px 3px;'; | 186 'background-size: 10px 3px;'; |
| 191 } | 187 } |
| 192 } | 188 } |
| 193 | 189 |
| 194 /// Processor that computes the HTML representation of a block of JavaScript | 190 /// Processor that computes the HTML representation of a block of JavaScript |
| 195 /// code and collects the source locations mapped in the code. | 191 /// code and collects the source locations mapped in the code. |
| 196 class CodeProcessor { | 192 class CodeProcessor { |
| 197 int lineIndex = 0; | 193 int lineIndex = 0; |
| 198 final String name; | 194 final String name; |
| 199 int currentJsSourceOffset = 0; | 195 int currentJsSourceOffset = 0; |
| 200 final SourceLocationCollection collection; | 196 final SourceLocationCollection collection; |
| 201 final Map<int, List<SourceLocation>> codeLocations = {}; | 197 final Map<int, List<SourceLocation>> codeLocations = {}; |
| 202 final CssColorScheme colorScheme; | 198 final CssColorScheme colorScheme; |
| 203 | 199 |
| 204 CodeProcessor(this.name, this.collection, | 200 CodeProcessor(this.name, this.collection, |
| 205 {this.colorScheme: const PatternCssColorScheme()}); | 201 {this.colorScheme: const PatternCssColorScheme()}); |
| 206 | 202 |
| 207 void addSourceLocation(int targetOffset, SourceLocation sourceLocation) { | 203 void addSourceLocation(int targetOffset, SourceLocation sourceLocation) { |
| 208 codeLocations.putIfAbsent(targetOffset, () => []).add(sourceLocation); | 204 codeLocations.putIfAbsent(targetOffset, () => []).add(sourceLocation); |
| 209 collection.registerSourceLocation(sourceLocation); | 205 collection.registerSourceLocation(sourceLocation); |
| 210 } | 206 } |
| 211 | 207 |
| 212 String convertToHtml(String text) { | 208 String convertToHtml(String text) { |
| 213 List<Annotation> annotations = <Annotation>[]; | 209 List<Annotation> annotations = <Annotation>[]; |
| 214 codeLocations.forEach((int codeOffset, List<SourceLocation> locations) { | 210 codeLocations.forEach((int codeOffset, List<SourceLocation> locations) { |
| 215 for (SourceLocation location in locations) { | 211 for (SourceLocation location in locations) { |
| 216 if (location != null) { | 212 if (location != null) { |
| 217 annotations.add(new Annotation( | 213 annotations.add(new Annotation( |
| 218 collection.getIndex(location), | 214 collection.getIndex(location), codeOffset, location.shortText)); |
| 219 codeOffset, | |
| 220 location.shortText)); | |
| 221 } | 215 } |
| 222 } | 216 } |
| 223 }); | 217 }); |
| 224 return convertAnnotatedCodeToHtml( | 218 return convertAnnotatedCodeToHtml(text, annotations, |
| 225 text, annotations, colorScheme: colorScheme, | 219 colorScheme: colorScheme, |
| 226 elementScheme: new HighlightLinkScheme(name), | 220 elementScheme: new HighlightLinkScheme(name), |
| 227 windowSize: 3); | 221 windowSize: 3); |
| 228 } | 222 } |
| 229 } | 223 } |
| 230 | 224 |
| 231 class ElementScheme { | 225 class ElementScheme { |
| 232 const ElementScheme(); | 226 const ElementScheme(); |
| 233 | 227 |
| 234 String getName(var id, Set ids) => null; | 228 String getName(var id, Set ids) => null; |
| 235 String getHref(var id, Set ids) => null; | 229 String getHref(var id, Set ids) => null; |
| (...skipping 27 matching lines...) Expand all Loading... |
| 263 String onmouseover = indices.map((i) => '\'$i\'').join(','); | 257 String onmouseover = indices.map((i) => '\'$i\'').join(','); |
| 264 return "highlight([${onmouseover}]);"; | 258 return "highlight([${onmouseover}]);"; |
| 265 } | 259 } |
| 266 | 260 |
| 267 @override | 261 @override |
| 268 String onMouseOver(int id, Set<int> indices) { | 262 String onMouseOver(int id, Set<int> indices) { |
| 269 return "highlight([]);"; | 263 return "highlight([]);"; |
| 270 } | 264 } |
| 271 } | 265 } |
| 272 | 266 |
| 273 String convertAnnotatedCodeToHtml( | 267 String convertAnnotatedCodeToHtml(String code, Iterable<Annotation> annotations, |
| 274 String code, | |
| 275 Iterable<Annotation> annotations, | |
| 276 {CssColorScheme colorScheme: const SingleColorScheme(), | 268 {CssColorScheme colorScheme: const SingleColorScheme(), |
| 277 ElementScheme elementScheme: const ElementScheme(), | 269 ElementScheme elementScheme: const ElementScheme(), |
| 278 int windowSize}) { | 270 int windowSize}) { |
| 279 StringBuffer htmlBuffer = new StringBuffer(); | 271 StringBuffer htmlBuffer = new StringBuffer(); |
| 280 List<CodeLine> lines = convertAnnotatedCodeToCodeLines( | 272 List<CodeLine> lines = convertAnnotatedCodeToCodeLines(code, annotations, |
| 281 code, annotations, | |
| 282 windowSize: windowSize); | 273 windowSize: windowSize); |
| 283 int lineNoWidth; | 274 int lineNoWidth; |
| 284 if (lines.isNotEmpty) { | 275 if (lines.isNotEmpty) { |
| 285 lineNoWidth = '${lines.last.lineNo + 1}'.length; | 276 lineNoWidth = '${lines.last.lineNo + 1}'.length; |
| 286 } | 277 } |
| 287 HtmlPrintContext context = new HtmlPrintContext( | 278 HtmlPrintContext context = new HtmlPrintContext( |
| 288 lineNoWidth: lineNoWidth, | 279 lineNoWidth: lineNoWidth, |
| 289 getAnnotationData: createAnnotationDataFunction( | 280 getAnnotationData: createAnnotationDataFunction( |
| 290 colorScheme: colorScheme, | 281 colorScheme: colorScheme, elementScheme: elementScheme)); |
| 291 elementScheme: elementScheme)); | |
| 292 for (CodeLine line in lines) { | 282 for (CodeLine line in lines) { |
| 293 line.printHtmlOn(htmlBuffer, context); | 283 line.printHtmlOn(htmlBuffer, context); |
| 294 } | 284 } |
| 295 return htmlBuffer.toString(); | 285 return htmlBuffer.toString(); |
| 296 } | 286 } |
| 297 | 287 |
| 298 List<CodeLine> convertAnnotatedCodeToCodeLines( | 288 List<CodeLine> convertAnnotatedCodeToCodeLines( |
| 299 String code, | 289 String code, Iterable<Annotation> annotations, |
| 300 Iterable<Annotation> annotations, | 290 {int startLine, int endLine, int windowSize, Uri uri}) { |
| 301 {int startLine, | |
| 302 int endLine, | |
| 303 int windowSize, | |
| 304 Uri uri}) { | |
| 305 | |
| 306 List<CodeLine> lines = <CodeLine>[]; | 291 List<CodeLine> lines = <CodeLine>[]; |
| 307 CodeLine currentLine; | 292 CodeLine currentLine; |
| 308 final List<Annotation> currentAnnotations = <Annotation>[]; | 293 final List<Annotation> currentAnnotations = <Annotation>[]; |
| 309 int offset = 0; | 294 int offset = 0; |
| 310 int lineIndex = 0; | 295 int lineIndex = 0; |
| 311 int firstLine; | 296 int firstLine; |
| 312 int lastLine; | 297 int lastLine; |
| 313 | 298 |
| 314 void addCode(String code) { | 299 void addCode(String code) { |
| 315 if (currentLine != null) { | 300 if (currentLine != null) { |
| 316 currentLine.codeBuffer.write(code); | 301 currentLine.codeBuffer.write(code); |
| 317 currentLine.codeParts.add( | 302 currentLine.codeParts |
| 318 new CodePart(currentAnnotations.toList(), code)); | 303 .add(new CodePart(currentAnnotations.toList(), code)); |
| 319 currentAnnotations.clear(); | 304 currentAnnotations.clear(); |
| 320 } | 305 } |
| 321 } | 306 } |
| 322 | 307 |
| 323 void addAnnotations(List<Annotation> annotations) { | 308 void addAnnotations(List<Annotation> annotations) { |
| 324 currentAnnotations.addAll(annotations); | 309 currentAnnotations.addAll(annotations); |
| 325 currentLine.annotations.addAll(annotations); | 310 currentLine.annotations.addAll(annotations); |
| 326 } | 311 } |
| 327 | 312 |
| 328 void beginLine(int currentOffset) { | 313 void beginLine(int currentOffset) { |
| 329 lines.add(currentLine = | 314 lines |
| 330 new CodeLine(lines.length, currentOffset, uri: uri)); | 315 .add(currentLine = new CodeLine(lines.length, currentOffset, uri: uri)); |
| 331 } | 316 } |
| 332 | 317 |
| 333 void endCurrentLocation() { | 318 void endCurrentLocation() { |
| 334 if (currentAnnotations.isNotEmpty) { | 319 if (currentAnnotations.isNotEmpty) { |
| 335 addCode(''); | 320 addCode(''); |
| 336 } | 321 } |
| 337 } | 322 } |
| 338 | 323 |
| 339 void addSubstring(int until, {bool isFirst: false, bool isLast: false}) { | 324 void addSubstring(int until, {bool isFirst: false, bool isLast: false}) { |
| 340 if (until <= offset) return; | 325 if (until <= offset) return; |
| (...skipping 28 matching lines...) Expand all Loading... |
| 369 void insertAnnotations(List<Annotation> annotations) { | 354 void insertAnnotations(List<Annotation> annotations) { |
| 370 endCurrentLocation(); | 355 endCurrentLocation(); |
| 371 addAnnotations(annotations); | 356 addAnnotations(annotations); |
| 372 if (annotations.last == null) { | 357 if (annotations.last == null) { |
| 373 endCurrentLocation(); | 358 endCurrentLocation(); |
| 374 } | 359 } |
| 375 } | 360 } |
| 376 | 361 |
| 377 Map<int, List<Annotation>> annotationMap = <int, List<Annotation>>{}; | 362 Map<int, List<Annotation>> annotationMap = <int, List<Annotation>>{}; |
| 378 for (Annotation annotation in annotations) { | 363 for (Annotation annotation in annotations) { |
| 379 annotationMap.putIfAbsent(annotation.codeOffset, () => <Annotation>[]) | 364 annotationMap |
| 380 .add(annotation); | 365 .putIfAbsent(annotation.codeOffset, () => <Annotation>[]) |
| 366 .add(annotation); |
| 381 } | 367 } |
| 382 | 368 |
| 383 bool first = true; | 369 bool first = true; |
| 384 for (int codeOffset in annotationMap.keys.toList()..sort()) { | 370 for (int codeOffset in annotationMap.keys.toList()..sort()) { |
| 385 List<Annotation> annotationList = annotationMap[codeOffset]; | 371 List<Annotation> annotationList = annotationMap[codeOffset]; |
| 386 addSubstring(codeOffset, isFirst: first); | 372 addSubstring(codeOffset, isFirst: first); |
| 387 insertAnnotations(annotationList); | 373 insertAnnotations(annotationList); |
| 388 first = false; | 374 first = false; |
| 389 } | 375 } |
| 390 | 376 |
| 391 addSubstring(code.length, isFirst: first, isLast: true); | 377 addSubstring(code.length, isFirst: first, isLast: true); |
| 392 endCurrentLocation(); | 378 endCurrentLocation(); |
| 393 | 379 |
| 394 int start = startLine ?? 0; | 380 int start = startLine ?? 0; |
| 395 int end = endLine ?? lines.length - 1; | 381 int end = endLine ?? lines.length - 1; |
| 396 if (windowSize != null) { | 382 if (windowSize != null) { |
| 397 start = Math.max(firstLine - windowSize, start); | 383 start = Math.max(firstLine - windowSize, start); |
| 398 end = Math.min(lastLine + windowSize, end); | 384 end = Math.min(lastLine + windowSize, end); |
| 399 } | 385 } |
| 400 return lines.sublist(start, end); | 386 return lines.sublist(start, end); |
| 401 } | 387 } |
| 402 | 388 |
| 403 /// Computes the HTML representation for a collection of JavaScript code blocks. | 389 /// Computes the HTML representation for a collection of JavaScript code blocks. |
| 404 String computeJsHtml(Iterable<SourceMapHtmlInfo> infoList) { | 390 String computeJsHtml(Iterable<SourceMapHtmlInfo> infoList) { |
| 405 | |
| 406 StringBuffer jsCodeBuffer = new StringBuffer(); | 391 StringBuffer jsCodeBuffer = new StringBuffer(); |
| 407 for (SourceMapHtmlInfo info in infoList) { | 392 for (SourceMapHtmlInfo info in infoList) { |
| 408 String name = info.sourceMapInfo.name; | 393 String name = info.sourceMapInfo.name; |
| 409 String html = info.codeProcessor.convertToHtml(info.sourceMapInfo.code); | 394 String html = info.codeProcessor.convertToHtml(info.sourceMapInfo.code); |
| 410 String onclick = 'show(\'$name\');'; | 395 String onclick = 'show(\'$name\');'; |
| 411 jsCodeBuffer.write( | 396 jsCodeBuffer |
| 412 '<h3 onclick="$onclick">JS code for: ${escape(name)}</h3>\n'); | 397 .write('<h3 onclick="$onclick">JS code for: ${escape(name)}</h3>\n'); |
| 413 jsCodeBuffer.write(''' | 398 jsCodeBuffer.write(''' |
| 414 <pre> | 399 <pre> |
| 415 $html | 400 $html |
| 416 </pre> | 401 </pre> |
| 417 '''); | 402 '''); |
| 418 } | 403 } |
| 419 return jsCodeBuffer.toString(); | 404 return jsCodeBuffer.toString(); |
| 420 } | 405 } |
| 421 | 406 |
| 422 /// Computes the HTML representation of the source mapping information for a | 407 /// Computes the HTML representation of the source mapping information for a |
| 423 /// collection of JavaScript code blocks. | 408 /// collection of JavaScript code blocks. |
| 424 String computeJsTraceHtml(Iterable<SourceMapHtmlInfo> infoList) { | 409 String computeJsTraceHtml(Iterable<SourceMapHtmlInfo> infoList) { |
| 425 StringBuffer jsTraceBuffer = new StringBuffer(); | 410 StringBuffer jsTraceBuffer = new StringBuffer(); |
| 426 for (SourceMapHtmlInfo info in infoList) { | 411 for (SourceMapHtmlInfo info in infoList) { |
| 427 String name = info.sourceMapInfo.name; | 412 String name = info.sourceMapInfo.name; |
| 428 String jsTrace = computeJsTraceHtmlPart( | 413 String jsTrace = computeJsTraceHtmlPart( |
| 429 info.sourceMapInfo.codePoints, info.sourceLocationCollection); | 414 info.sourceMapInfo.codePoints, info.sourceLocationCollection); |
| 430 jsTraceBuffer.write(''' | 415 jsTraceBuffer.write(''' |
| 431 <div name="$name" class="js-trace-buffer" style="display:none;"> | 416 <div name="$name" class="js-trace-buffer" style="display:none;"> |
| 432 <h3>Trace for: ${escape(name)}</h3> | 417 <h3>Trace for: ${escape(name)}</h3> |
| 433 $jsTrace | 418 $jsTrace |
| 434 </div> | 419 </div> |
| 435 '''); | 420 '''); |
| 436 } | 421 } |
| 437 return jsTraceBuffer.toString(); | 422 return jsTraceBuffer.toString(); |
| 438 } | 423 } |
| 439 | 424 |
| 440 /// Computes the HTML information for the [info]. | 425 /// Computes the HTML information for the [info]. |
| 441 SourceMapHtmlInfo createHtmlInfo(SourceLocationCollection collection, | 426 SourceMapHtmlInfo createHtmlInfo( |
| 442 SourceMapInfo info) { | 427 SourceLocationCollection collection, SourceMapInfo info) { |
| 443 js.Node node = info.node; | 428 js.Node node = info.node; |
| 444 String code = info.code; | 429 String code = info.code; |
| 445 String name = info.name; | 430 String name = info.name; |
| 446 SourceLocationCollection subcollection = | 431 SourceLocationCollection subcollection = |
| 447 new SourceLocationCollection(collection); | 432 new SourceLocationCollection(collection); |
| 448 CodeProcessor codeProcessor = new CodeProcessor(name, subcollection); | 433 CodeProcessor codeProcessor = new CodeProcessor(name, subcollection); |
| 449 for (js.Node node in info.nodeMap.nodes) { | 434 for (js.Node node in info.nodeMap.nodes) { |
| 450 info.nodeMap[node].forEach( | 435 info.nodeMap[node] |
| 451 (int targetOffset, List<SourceLocation> sourceLocations) { | 436 .forEach((int targetOffset, List<SourceLocation> sourceLocations) { |
| 452 for (SourceLocation sourceLocation in sourceLocations) { | 437 for (SourceLocation sourceLocation in sourceLocations) { |
| 453 codeProcessor.addSourceLocation(targetOffset, sourceLocation); | 438 codeProcessor.addSourceLocation(targetOffset, sourceLocation); |
| 454 } | 439 } |
| 455 }); | 440 }); |
| 456 } | 441 } |
| 457 return new SourceMapHtmlInfo(info, codeProcessor, subcollection); | 442 return new SourceMapHtmlInfo(info, codeProcessor, subcollection); |
| 458 } | 443 } |
| 459 | 444 |
| 460 /// Outputs a HTML file in [jsMapHtmlUri] containing an interactive | 445 /// Outputs a HTML file in [jsMapHtmlUri] containing an interactive |
| 461 /// visualization of the source mapping information in [infoList] computed | 446 /// visualization of the source mapping information in [infoList] computed |
| 462 /// with the [sourceMapProcessor]. | 447 /// with the [sourceMapProcessor]. |
| 463 void createTraceSourceMapHtml(Uri jsMapHtmlUri, | 448 void createTraceSourceMapHtml(Uri jsMapHtmlUri, |
| 464 SourceMapProcessor sourceMapProcessor, | 449 SourceMapProcessor sourceMapProcessor, Iterable<SourceMapInfo> infoList) { |
| 465 Iterable<SourceMapInfo> infoList) { | |
| 466 SourceFileManager sourceFileManager = sourceMapProcessor.sourceFileManager; | 450 SourceFileManager sourceFileManager = sourceMapProcessor.sourceFileManager; |
| 467 SourceLocationCollection collection = new SourceLocationCollection(); | 451 SourceLocationCollection collection = new SourceLocationCollection(); |
| 468 List<SourceMapHtmlInfo> htmlInfoList = <SourceMapHtmlInfo>[]; | 452 List<SourceMapHtmlInfo> htmlInfoList = <SourceMapHtmlInfo>[]; |
| 469 for (SourceMapInfo info in infoList) { | 453 for (SourceMapInfo info in infoList) { |
| 470 htmlInfoList.add(createHtmlInfo(collection, info)); | 454 htmlInfoList.add(createHtmlInfo(collection, info)); |
| 471 } | 455 } |
| 472 | 456 |
| 473 String jsCode = computeJsHtml(htmlInfoList); | 457 String jsCode = computeJsHtml(htmlInfoList); |
| 474 String dartCode = computeDartHtml(sourceFileManager, htmlInfoList); | 458 String dartCode = computeDartHtml(sourceFileManager, htmlInfoList); |
| 475 | 459 |
| 476 String jsTraceHtml = computeJsTraceHtml(htmlInfoList); | 460 String jsTraceHtml = computeJsTraceHtml(htmlInfoList); |
| 477 outputJsDartTrace(jsMapHtmlUri, jsCode, dartCode, jsTraceHtml); | 461 outputJsDartTrace(jsMapHtmlUri, jsCode, dartCode, jsTraceHtml); |
| 478 print('Trace source map html generated: $jsMapHtmlUri'); | 462 print('Trace source map html generated: $jsMapHtmlUri'); |
| 479 } | 463 } |
| 480 | 464 |
| 481 /// Computes the HTML representation for the Dart code snippets referenced in | 465 /// Computes the HTML representation for the Dart code snippets referenced in |
| 482 /// [infoList]. | 466 /// [infoList]. |
| 483 String computeDartHtml( | 467 String computeDartHtml( |
| 484 SourceFileManager sourceFileManager, | 468 SourceFileManager sourceFileManager, Iterable<SourceMapHtmlInfo> infoList) { |
| 485 Iterable<SourceMapHtmlInfo> infoList) { | |
| 486 | |
| 487 StringBuffer dartCodeBuffer = new StringBuffer(); | 469 StringBuffer dartCodeBuffer = new StringBuffer(); |
| 488 for (SourceMapHtmlInfo info in infoList) { | 470 for (SourceMapHtmlInfo info in infoList) { |
| 489 dartCodeBuffer.write(computeDartHtmlPart(info.sourceMapInfo.name, | 471 dartCodeBuffer.write(computeDartHtmlPart(info.sourceMapInfo.name, |
| 490 sourceFileManager, info.sourceLocationCollection)); | 472 sourceFileManager, info.sourceLocationCollection)); |
| 491 } | 473 } |
| 492 return dartCodeBuffer.toString(); | 474 return dartCodeBuffer.toString(); |
| 493 | |
| 494 } | 475 } |
| 495 | 476 |
| 496 /// Computes the HTML representation for the Dart code snippets in [collection]. | 477 /// Computes the HTML representation for the Dart code snippets in [collection]. |
| 497 String computeDartHtmlPart(String name, | 478 String computeDartHtmlPart(String name, SourceFileManager sourceFileManager, |
| 498 SourceFileManager sourceFileManager, | 479 SourceLocationCollection collection, |
| 499 SourceLocationCollection collection, | 480 {bool showAsBlock: false}) { |
| 500 {bool showAsBlock: false}) { | |
| 501 const int windowSize = 3; | 481 const int windowSize = 3; |
| 502 StringBuffer dartCodeBuffer = new StringBuffer(); | 482 StringBuffer dartCodeBuffer = new StringBuffer(); |
| 503 Map<Uri, Map<int, List<SourceLocation>>> sourceLocationMap = {}; | 483 Map<Uri, Map<int, List<SourceLocation>>> sourceLocationMap = {}; |
| 504 collection.sourceLocations.forEach((SourceLocation sourceLocation) { | 484 collection.sourceLocations.forEach((SourceLocation sourceLocation) { |
| 505 Map<int, List<SourceLocation>> uriMap = | 485 Map<int, List<SourceLocation>> uriMap = |
| 506 sourceLocationMap.putIfAbsent(sourceLocation.sourceUri, () => {}); | 486 sourceLocationMap.putIfAbsent(sourceLocation.sourceUri, () => {}); |
| 507 List<SourceLocation> lineList = | 487 List<SourceLocation> lineList = |
| 508 uriMap.putIfAbsent(sourceLocation.line, () => []); | 488 uriMap.putIfAbsent(sourceLocation.line, () => []); |
| 509 lineList.add(sourceLocation); | 489 lineList.add(sourceLocation); |
| 510 }); | 490 }); |
| 511 sourceLocationMap.forEach((Uri uri, Map<int, List<SourceLocation>> uriMap) { | 491 sourceLocationMap.forEach((Uri uri, Map<int, List<SourceLocation>> uriMap) { |
| 512 SourceFile sourceFile = sourceFileManager.getSourceFile(uri); | 492 SourceFile sourceFile = sourceFileManager.getSourceFile(uri); |
| 513 if (sourceFile == null) return; | 493 if (sourceFile == null) return; |
| 514 StringBuffer codeBuffer = new StringBuffer(); | 494 StringBuffer codeBuffer = new StringBuffer(); |
| 515 | 495 |
| 516 int firstLineIndex; | 496 int firstLineIndex; |
| 517 int lastLineIndex; | 497 int lastLineIndex; |
| 518 List<int> lineIndices = uriMap.keys.toList()..sort(); | 498 List<int> lineIndices = uriMap.keys.toList()..sort(); |
| 519 int lineNoWidth; | 499 int lineNoWidth; |
| 520 if (lineIndices.isNotEmpty) { | 500 if (lineIndices.isNotEmpty) { |
| 521 lineNoWidth = '${lineIndices.last + windowSize + 1}'.length; | 501 lineNoWidth = '${lineIndices.last + windowSize + 1}'.length; |
| 522 } | 502 } |
| 523 | 503 |
| 524 void flush() { | 504 void flush() { |
| 525 if (firstLineIndex != null && lastLineIndex != null) { | 505 if (firstLineIndex != null && lastLineIndex != null) { |
| 526 dartCodeBuffer.write( | 506 dartCodeBuffer.write('<h4>${uri.pathSegments.last}, ' |
| 527 '<h4>${uri.pathSegments.last}, ' | |
| 528 '${firstLineIndex - windowSize + 1}-' | 507 '${firstLineIndex - windowSize + 1}-' |
| 529 '${lastLineIndex + windowSize + 1}' | 508 '${lastLineIndex + windowSize + 1}' |
| 530 '</h4>\n'); | 509 '</h4>\n'); |
| 531 dartCodeBuffer.write('<pre>\n'); | 510 dartCodeBuffer.write('<pre>\n'); |
| 532 for (int line = firstLineIndex - windowSize; | 511 for (int line = firstLineIndex - windowSize; |
| 533 line < firstLineIndex; | 512 line < firstLineIndex; |
| 534 line++) { | 513 line++) { |
| 535 if (line >= 0) { | 514 if (line >= 0) { |
| 536 dartCodeBuffer.write(lineNumber(line, width: lineNoWidth)); | 515 dartCodeBuffer.write(lineNumber(line, width: lineNoWidth)); |
| 537 dartCodeBuffer.write(sourceFile.getLineText(line)); | 516 dartCodeBuffer.write(sourceFile.getLineText(line)); |
| 538 } | 517 } |
| 539 } | 518 } |
| 540 dartCodeBuffer.write(codeBuffer); | 519 dartCodeBuffer.write(codeBuffer); |
| 541 for (int line = lastLineIndex + 1; | 520 for (int line = lastLineIndex + 1; |
| 542 line <= lastLineIndex + windowSize; | 521 line <= lastLineIndex + windowSize; |
| 543 line++) { | 522 line++) { |
| 544 if (line < sourceFile.lines) { | 523 if (line < sourceFile.lines) { |
| 545 dartCodeBuffer.write(lineNumber(line, width: lineNoWidth)); | 524 dartCodeBuffer.write(lineNumber(line, width: lineNoWidth)); |
| 546 dartCodeBuffer.write(sourceFile.getLineText(line)); | 525 dartCodeBuffer.write(sourceFile.getLineText(line)); |
| 547 } | 526 } |
| 548 } | 527 } |
| 549 dartCodeBuffer.write('</pre>\n'); | 528 dartCodeBuffer.write('</pre>\n'); |
| 550 firstLineIndex = null; | 529 firstLineIndex = null; |
| 551 lastLineIndex = null; | 530 lastLineIndex = null; |
| 552 } | 531 } |
| 553 codeBuffer.clear(); | 532 codeBuffer.clear(); |
| 554 } | 533 } |
| 555 | 534 |
| 556 lineIndices.forEach((int lineIndex) { | 535 lineIndices.forEach((int lineIndex) { |
| 557 List<SourceLocation> locations = uriMap[lineIndex]; | 536 List<SourceLocation> locations = uriMap[lineIndex]; |
| 558 if (lastLineIndex != null && | 537 if (lastLineIndex != null && lastLineIndex + windowSize * 4 < lineIndex) { |
| 559 lastLineIndex + windowSize * 4 < lineIndex) { | |
| 560 flush(); | 538 flush(); |
| 561 } | 539 } |
| 562 if (firstLineIndex == null) { | 540 if (firstLineIndex == null) { |
| 563 firstLineIndex = lineIndex; | 541 firstLineIndex = lineIndex; |
| 564 } else { | 542 } else { |
| 565 for (int line = lastLineIndex + 1; line < lineIndex; line++) { | 543 for (int line = lastLineIndex + 1; line < lineIndex; line++) { |
| 566 codeBuffer.write(lineNumber(line, width: lineNoWidth)); | 544 codeBuffer.write(lineNumber(line, width: lineNoWidth)); |
| 567 codeBuffer.write(sourceFile.getLineText(line)); | 545 codeBuffer.write(sourceFile.getLineText(line)); |
| 568 } | 546 } |
| 569 } | 547 } |
| 570 String line = sourceFile.getLineText(lineIndex); | 548 String line = sourceFile.getLineText(lineIndex); |
| 571 locations.sort((a, b) => a.offset.compareTo(b.offset)); | 549 locations.sort((a, b) => a.offset.compareTo(b.offset)); |
| 572 for (int i = 0; i < locations.length; i++) { | 550 for (int i = 0; i < locations.length; i++) { |
| 573 SourceLocation sourceLocation = locations[i]; | 551 SourceLocation sourceLocation = locations[i]; |
| 574 int index = collection.getIndex(sourceLocation); | 552 int index = collection.getIndex(sourceLocation); |
| 575 int start = sourceLocation.column; | 553 int start = sourceLocation.column; |
| 576 int end = line.length; | 554 int end = line.length; |
| 577 if (i + 1 < locations.length) { | 555 if (i + 1 < locations.length) { |
| 578 end = locations[i + 1].column; | 556 end = locations[i + 1].column; |
| 579 } | 557 } |
| 580 if (i == 0) { | 558 if (i == 0) { |
| 581 codeBuffer.write(lineNumber(lineIndex, width: lineNoWidth)); | 559 codeBuffer.write(lineNumber(lineIndex, width: lineNoWidth)); |
| 582 codeBuffer.write(line.substring(0, start)); | 560 codeBuffer.write(line.substring(0, start)); |
| 583 } | 561 } |
| 584 codeBuffer.write( | 562 codeBuffer |
| 585 '<a name="${index}" style="background:${toPattern(index)};" ' | 563 .write('<a name="${index}" style="background:${toPattern(index)};" ' |
| 586 'title="[${lineIndex + 1},${start + 1}]" ' | 564 'title="[${lineIndex + 1},${start + 1}]" ' |
| 587 'onmouseover="highlight(\'$index\');" ' | 565 'onmouseover="highlight(\'$index\');" ' |
| 588 'onmouseout="highlight();">'); | 566 'onmouseout="highlight();">'); |
| 589 codeBuffer.write(line.substring(start, end)); | 567 codeBuffer.write(line.substring(start, end)); |
| 590 codeBuffer.write('</a>'); | 568 codeBuffer.write('</a>'); |
| 591 } | 569 } |
| 592 lastLineIndex = lineIndex; | 570 lastLineIndex = lineIndex; |
| 593 }); | 571 }); |
| 594 | 572 |
| 595 flush(); | 573 flush(); |
| 596 }); | 574 }); |
| 597 String display = showAsBlock ? 'block' : 'none'; | 575 String display = showAsBlock ? 'block' : 'none'; |
| 598 return ''' | 576 return ''' |
| 599 <div name="$name" class="dart-buffer" style="display:$display;"> | 577 <div name="$name" class="dart-buffer" style="display:$display;"> |
| 600 <h3>Dart code for: ${escape(name)}</h3> | 578 <h3>Dart code for: ${escape(name)}</h3> |
| 601 ${dartCodeBuffer} | 579 ${dartCodeBuffer} |
| 602 </div>'''; | 580 </div>'''; |
| 603 } | 581 } |
| 604 | 582 |
| 605 /// Computes a HTML visualization of the [codePoints]. | 583 /// Computes a HTML visualization of the [codePoints]. |
| 606 String computeJsTraceHtmlPart(List<CodePoint> codePoints, | 584 String computeJsTraceHtmlPart( |
| 607 SourceLocationCollection collection) { | 585 List<CodePoint> codePoints, SourceLocationCollection collection) { |
| 608 StringBuffer buffer = new StringBuffer(); | 586 StringBuffer buffer = new StringBuffer(); |
| 609 buffer.write('<table style="width:100%;">'); | 587 buffer.write('<table style="width:100%;">'); |
| 610 buffer.write( | 588 buffer.write('<tr><th>Node kind</th><th>JS code @ offset</th>' |
| 611 '<tr><th>Node kind</th><th>JS code @ offset</th>' | |
| 612 '<th>Dart code @ mapped location</th><th>file:position:name</th></tr>'); | 589 '<th>Dart code @ mapped location</th><th>file:position:name</th></tr>'); |
| 613 codePoints.forEach((CodePoint codePoint) { | 590 codePoints.forEach((CodePoint codePoint) { |
| 614 String jsCode = truncate(codePoint.jsCode, 50); | 591 String jsCode = truncate(codePoint.jsCode, 50); |
| 615 if (codePoint.sourceLocation != null) { | 592 if (codePoint.sourceLocation != null) { |
| 616 int index = collection.getIndex(codePoint.sourceLocation); | 593 int index = collection.getIndex(codePoint.sourceLocation); |
| 617 if (index != null) { | 594 if (index != null) { |
| 618 String style = ''; | 595 String style = ''; |
| 619 if (!codePoint.isMissing) { | 596 if (!codePoint.isMissing) { |
| 620 style = 'style="background:${toColorCss(index)};" '; | 597 style = 'style="background:${toColorCss(index)};" '; |
| 621 } | 598 } |
| 622 buffer.write('<tr $style' | 599 buffer.write('<tr $style' |
| 623 'name="trace$index" ' | 600 'name="trace$index" ' |
| 624 'onmouseover="highlight([${index}]);"' | 601 'onmouseover="highlight([${index}]);"' |
| 625 'onmouseout="highlight([]);">'); | 602 'onmouseout="highlight([]);">'); |
| 626 } else { | 603 } else { |
| 627 buffer.write('<tr>'); | 604 buffer.write('<tr>'); |
| 628 print('${codePoint.sourceLocation} not found in '); | 605 print('${codePoint.sourceLocation} not found in '); |
| 629 collection.sourceLocationIndexMap.keys | 606 collection.sourceLocationIndexMap.keys |
| 630 .where((l) => l.sourceUri == codePoint.sourceLocation.sourceUri) | 607 .where((l) => l.sourceUri == codePoint.sourceLocation.sourceUri) |
| 631 .forEach((l) => print(' $l')); | 608 .forEach((l) => print(' $l')); |
| 632 } | 609 } |
| 633 } else { | 610 } else { |
| 634 buffer.write('<tr>'); | 611 buffer.write('<tr>'); |
| 635 } | 612 } |
| 636 buffer.write('<td>${codePoint.kind}</td>'); | 613 buffer.write('<td>${codePoint.kind}</td>'); |
| 637 buffer.write('<td class="code">${jsCode}</td>'); | 614 buffer.write('<td class="code">${jsCode}</td>'); |
| 638 if (codePoint.sourceLocation == null) { | 615 if (codePoint.sourceLocation == null) { |
| 639 //buffer.write('<td></td>'); | 616 //buffer.write('<td></td>'); |
| 640 } else { | 617 } else { |
| 641 String dartCode = truncate(codePoint.dartCode, 50); | 618 String dartCode = truncate(codePoint.dartCode, 50); |
| 642 buffer.write('<td class="code">${dartCode}</td>'); | 619 buffer.write('<td class="code">${dartCode}</td>'); |
| 643 buffer.write('<td>${escape(codePoint.sourceLocation.shortText)}</td>'); | 620 buffer.write('<td>${escape(codePoint.sourceLocation.shortText)}</td>'); |
| 644 } | 621 } |
| 645 buffer.write('</tr>'); | 622 buffer.write('</tr>'); |
| 646 }); | 623 }); |
| 647 buffer.write('</table>'); | 624 buffer.write('</table>'); |
| 648 | 625 |
| 649 return buffer.toString(); | 626 return buffer.toString(); |
| 650 } | 627 } |
| OLD | NEW |