| 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'; |
| 11 import 'dart:math' as Math; | 11 import 'dart:math' as Math; |
| 12 | 12 |
| 13 import 'package:compiler/src/io/source_file.dart'; | 13 import 'package:compiler/src/io/source_file.dart'; |
| 14 import 'package:compiler/src/io/source_information.dart'; | 14 import 'package:compiler/src/io/source_information.dart'; |
| 15 import 'package:compiler/src/js/js.dart' as js; | 15 import 'package:compiler/src/js/js.dart' as js; |
| 16 | 16 |
| 17 import 'colors.dart'; | 17 import 'colors.dart'; |
| 18 import 'sourcemap_helper.dart'; | 18 import 'sourcemap_helper.dart'; |
| 19 import 'sourcemap_html_templates.dart'; | 19 import 'sourcemap_html_templates.dart'; |
| 20 import 'html_parts.dart'; | 20 import 'html_parts.dart'; |
| 21 | 21 |
| 22 /// Truncate [input] to [length], adding '...' if truncated. | 22 /// Truncate [input] to [length], adding '...' if truncated. |
| 23 String truncate(String input, int length) { | 23 String truncate(String input, int length) { |
| 24 if (input.length > length) { | 24 if (input.length > length) { |
| 25 return '${input.substring(0, length - 3)}...'; | 25 return '${input.substring(0, length - 3)}...'; |
| 26 } | 26 } |
| 27 return input; | 27 return input; |
| 28 } | 28 } |
| 29 | 29 |
| 30 const int HUE_COUNT = 24; |
| 31 |
| 30 /// Returns the [index]th color for visualization. | 32 /// Returns the [index]th color for visualization. |
| 31 HSV toColor(int index) { | 33 HSV toColor(int index) { |
| 32 int hueCount = 24; | 34 double h = 360.0 * (index % HUE_COUNT) / HUE_COUNT; |
| 33 double h = 360.0 * (index % hueCount) / hueCount; | |
| 34 double v = 1.0; | 35 double v = 1.0; |
| 35 double s = 0.5; | 36 double s = 0.5; |
| 36 return new HSV(h, s, v); | 37 return new HSV(h, s, v); |
| 37 } | 38 } |
| 38 | 39 |
| 39 /// Return the CSS color value for the [index]th color. | 40 /// Return the CSS color value for the [index]th color. |
| 40 String toColorCss(int index) { | 41 String toColorCss(int index) { |
| 41 return toColor(index).toCss; | 42 return toColor(index).toCss; |
| 42 } | 43 } |
| 43 | 44 |
| 44 /// Return the CSS color value for the [index]th span. | 45 /// Return the CSS color value for the [index]th span. |
| 45 String toPattern(int index) { | 46 String toPattern(int index) { |
| 46 /// 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 |
| 47 /// same source location. | 48 /// same source location. |
| 48 HSV startColor = toColor(index); | 49 HSV startColor = toColor(index); |
| 49 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); |
| 50 return 'linear-gradient(to right, ${startColor.toCss}, ${endColor.toCss})'; | 51 return 'linear-gradient(to right, ${startColor.toCss}, ${endColor.toCss})'; |
| 51 } | 52 } |
| 52 | 53 |
| 53 /// 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 |
| 54 /// line numbers will be prefixed with spaces to match the width. | 55 /// line numbers will be prefixed with spaces to match the width. |
| 55 String lineNumber(int index, {int width, bool useNbsp: false}) { | 56 String lineNumber(int index, |
| 57 {int width, |
| 58 bool useNbsp: false, |
| 59 String className}) { |
| 60 if (className == null) { |
| 61 className = 'lineNumber'; |
| 62 } |
| 56 String text = '${index + 1}'; | 63 String text = '${index + 1}'; |
| 57 String padding = useNbsp ? ' ' : ' '; | 64 String padding = useNbsp ? ' ' : ' '; |
| 58 if (width != null && text.length < width) { | 65 if (width != null && text.length < width) { |
| 59 text = (padding * (width - text.length)) + text; | 66 text = (padding * (width - text.length)) + text; |
| 60 } | 67 } |
| 61 return '<span class="lineNumber">$text$padding</span>'; | 68 return '<span class="$className">$text$padding</span>'; |
| 62 } | 69 } |
| 63 | 70 |
| 64 /// Return the html escaped [text]. | 71 /// Return the html escaped [text]. |
| 65 String escape(String text) { | 72 String escape(String text) { |
| 66 return const HtmlEscape().convert(text); | 73 return const HtmlEscape().convert(text); |
| 67 } | 74 } |
| 68 | 75 |
| 69 /// Information needed to generate HTML for a single [SourceMapInfo]. | 76 /// Information needed to generate HTML for a single [SourceMapInfo]. |
| 70 class SourceMapHtmlInfo { | 77 class SourceMapHtmlInfo { |
| 71 final SourceMapInfo sourceMapInfo; | 78 final SourceMapInfo sourceMapInfo; |
| (...skipping 142 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 214 } | 221 } |
| 215 } | 222 } |
| 216 }); | 223 }); |
| 217 return convertAnnotatedCodeToHtml( | 224 return convertAnnotatedCodeToHtml( |
| 218 text, annotations, colorScheme: colorScheme, | 225 text, annotations, colorScheme: colorScheme, |
| 219 elementScheme: new HighlightLinkScheme(name), | 226 elementScheme: new HighlightLinkScheme(name), |
| 220 windowSize: 3); | 227 windowSize: 3); |
| 221 } | 228 } |
| 222 } | 229 } |
| 223 | 230 |
| 224 class Annotation { | |
| 225 final id; | |
| 226 final int codeOffset; | |
| 227 final String title; | |
| 228 | |
| 229 Annotation(this.id, this.codeOffset, this.title); | |
| 230 } | |
| 231 | |
| 232 class ElementScheme { | 231 class ElementScheme { |
| 233 const ElementScheme(); | 232 const ElementScheme(); |
| 234 | 233 |
| 235 String getName(var id, Set ids) => null; | 234 String getName(var id, Set ids) => null; |
| 236 String getHref(var id, Set ids) => null; | 235 String getHref(var id, Set ids) => null; |
| 237 String onClick(var id, Set ids) => null; | 236 String onClick(var id, Set ids) => null; |
| 238 String onMouseOver(var id, Set ids) => null; | 237 String onMouseOver(var id, Set ids) => null; |
| 239 String onMouseOut(var id, Set ids) => null; | 238 String onMouseOut(var id, Set ids) => null; |
| 240 } | 239 } |
| 241 | 240 |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 273 | 272 |
| 274 String convertAnnotatedCodeToHtml( | 273 String convertAnnotatedCodeToHtml( |
| 275 String code, | 274 String code, |
| 276 Iterable<Annotation> annotations, | 275 Iterable<Annotation> annotations, |
| 277 {CssColorScheme colorScheme: const SingleColorScheme(), | 276 {CssColorScheme colorScheme: const SingleColorScheme(), |
| 278 ElementScheme elementScheme: const ElementScheme(), | 277 ElementScheme elementScheme: const ElementScheme(), |
| 279 int windowSize}) { | 278 int windowSize}) { |
| 280 StringBuffer htmlBuffer = new StringBuffer(); | 279 StringBuffer htmlBuffer = new StringBuffer(); |
| 281 List<CodeLine> lines = convertAnnotatedCodeToCodeLines( | 280 List<CodeLine> lines = convertAnnotatedCodeToCodeLines( |
| 282 code, annotations, | 281 code, annotations, |
| 283 colorScheme: colorScheme, | |
| 284 elementScheme: elementScheme, | |
| 285 windowSize: windowSize); | 282 windowSize: windowSize); |
| 286 int lineNoWidth; | 283 int lineNoWidth; |
| 287 if (lines.isNotEmpty) { | 284 if (lines.isNotEmpty) { |
| 288 lineNoWidth = '${lines.last.lineNo + 1}'.length; | 285 lineNoWidth = '${lines.last.lineNo + 1}'.length; |
| 289 } | 286 } |
| 290 HtmlPrintContext context = new HtmlPrintContext(lineNoWidth: lineNoWidth); | 287 HtmlPrintContext context = new HtmlPrintContext( |
| 288 lineNoWidth: lineNoWidth, |
| 289 getAnnotationData: createAnnotationDataFunction( |
| 290 colorScheme: colorScheme, |
| 291 elementScheme: elementScheme)); |
| 291 for (CodeLine line in lines) { | 292 for (CodeLine line in lines) { |
| 292 line.printHtmlOn(htmlBuffer, context); | 293 line.printHtmlOn(htmlBuffer, context); |
| 293 } | 294 } |
| 294 return htmlBuffer.toString(); | 295 return htmlBuffer.toString(); |
| 295 } | 296 } |
| 296 | 297 |
| 297 List<CodeLine> convertAnnotatedCodeToCodeLines( | 298 List<CodeLine> convertAnnotatedCodeToCodeLines( |
| 298 String code, | 299 String code, |
| 299 Iterable<Annotation> annotations, | 300 Iterable<Annotation> annotations, |
| 300 {CssColorScheme colorScheme: const SingleColorScheme(), | 301 {int startLine, |
| 301 ElementScheme elementScheme: const ElementScheme(), | 302 int endLine, |
| 302 int windowSize}) { | 303 int windowSize, |
| 304 Uri uri}) { |
| 303 | 305 |
| 304 List<CodeLine> lines = <CodeLine>[]; | 306 List<CodeLine> lines = <CodeLine>[]; |
| 305 CodeLine currentLine; | 307 CodeLine currentLine; |
| 308 final List<Annotation> currentAnnotations = <Annotation>[]; |
| 306 int offset = 0; | 309 int offset = 0; |
| 307 int lineIndex = 0; | 310 int lineIndex = 0; |
| 308 int firstLine; | 311 int firstLine; |
| 309 int lastLine; | 312 int lastLine; |
| 310 bool pendingSourceLocationsEnd = false; | |
| 311 | 313 |
| 312 void write(String code, HtmlPart html) { | 314 void addCode(String code) { |
| 313 if (currentLine != null) { | 315 if (currentLine != null) { |
| 314 currentLine.codeBuffer.write(code); | 316 currentLine.codeBuffer.write(code); |
| 315 currentLine.htmlParts.add(html); | 317 currentLine.codeParts.add( |
| 318 new CodePart(currentAnnotations.toList(), code)); |
| 319 currentAnnotations.clear(); |
| 316 } | 320 } |
| 317 } | 321 } |
| 318 | 322 |
| 319 void startLine(int currentOffset) { | 323 void addAnnotations(List<Annotation> annotations) { |
| 320 lines.add(currentLine = new CodeLine(lines.length, currentOffset)); | 324 currentAnnotations.addAll(annotations); |
| 325 currentLine.annotations.addAll(annotations); |
| 326 } |
| 327 |
| 328 void beginLine(int currentOffset) { |
| 329 lines.add(currentLine = |
| 330 new CodeLine(lines.length, currentOffset, uri: uri)); |
| 321 } | 331 } |
| 322 | 332 |
| 323 void endCurrentLocation() { | 333 void endCurrentLocation() { |
| 324 if (pendingSourceLocationsEnd) { | 334 if (currentAnnotations.isNotEmpty) { |
| 325 write('', const ConstHtmlPart('</a>')); | 335 addCode(''); |
| 326 } | 336 } |
| 327 pendingSourceLocationsEnd = false; | |
| 328 } | 337 } |
| 329 | 338 |
| 330 void addSubstring(int until, {bool isFirst: false, bool isLast: false}) { | 339 void addSubstring(int until, {bool isFirst: false, bool isLast: false}) { |
| 331 if (until <= offset) return; | 340 if (until <= offset) return; |
| 332 if (offset >= code.length) return; | 341 if (offset >= code.length) return; |
| 333 | 342 |
| 334 String substring = code.substring(offset, until); | 343 String substring = code.substring(offset, until); |
| 335 bool first = true; | 344 bool first = true; |
| 336 | 345 |
| 337 if (isLast) { | 346 if (isLast) { |
| 338 lastLine = lineIndex; | 347 lastLine = lineIndex; |
| 339 } | 348 } |
| 340 int localOffset = 0; | 349 int localOffset = 0; |
| 341 if (isFirst) { | 350 if (isFirst) { |
| 342 startLine(offset + localOffset); | 351 beginLine(offset + localOffset); |
| 343 } | 352 } |
| 344 for (String line in substring.split('\n')) { | 353 for (String line in substring.split('\n')) { |
| 345 if (!first) { | 354 if (!first) { |
| 346 endCurrentLocation(); | 355 endCurrentLocation(); |
| 347 write('', const NewLine()); | |
| 348 lineIndex++; | 356 lineIndex++; |
| 349 startLine(offset + localOffset); | 357 beginLine(offset + localOffset); |
| 350 } | 358 } |
| 351 if (pendingSourceLocationsEnd && !colorScheme.showLocationAsSpan) { | 359 addCode(line); |
| 352 if (line.isNotEmpty) { | |
| 353 String before = line.substring(0, 1); | |
| 354 write(before, new HtmlText(before)); | |
| 355 endCurrentLocation(); | |
| 356 String after = line.substring(1); | |
| 357 write(after, new HtmlText(after)); | |
| 358 } | |
| 359 } else { | |
| 360 write(line, new HtmlText(line)); | |
| 361 } | |
| 362 first = false; | 360 first = false; |
| 363 localOffset += line.length + 1; | 361 localOffset += line.length + 1; |
| 364 } | 362 } |
| 365 if (isFirst) { | 363 if (isFirst) { |
| 366 firstLine = lineIndex; | 364 firstLine = lineIndex; |
| 367 } | 365 } |
| 368 offset = until; | 366 offset = until; |
| 369 } | 367 } |
| 370 | 368 |
| 371 void insertAnnotations(List<Annotation> annotations) { | 369 void insertAnnotations(List<Annotation> annotations) { |
| 372 endCurrentLocation(); | 370 endCurrentLocation(); |
| 373 | 371 addAnnotations(annotations); |
| 374 String color; | |
| 375 var id; | |
| 376 String title; | |
| 377 if (annotations.length == 1) { | |
| 378 Annotation annotation = annotations.single; | |
| 379 if (annotation != null) { | |
| 380 id = annotation.id; | |
| 381 color = colorScheme.singleLocationToCssColor(id); | |
| 382 title = annotation.title; | |
| 383 } | |
| 384 } else { | |
| 385 id = annotations.first.id; | |
| 386 List ids = []; | |
| 387 for (Annotation annotation in annotations) { | |
| 388 ids.add(annotation.id); | |
| 389 } | |
| 390 color = colorScheme.multiLocationToCssColor(ids); | |
| 391 title = annotations.map((l) => l.title).join(','); | |
| 392 } | |
| 393 if (id != null) { | |
| 394 Set ids = annotations.map((l) => l.id).toSet(); | |
| 395 String name = elementScheme.getName(id, ids); | |
| 396 String href = elementScheme.getHref(id, ids); | |
| 397 String onclick = elementScheme.onClick(id, ids); | |
| 398 String onmouseover = elementScheme.onMouseOver(id, ids); | |
| 399 String onmouseout = elementScheme.onMouseOut(id, ids); | |
| 400 write('', new AnchorHtmlPart( | |
| 401 color: color, | |
| 402 name: name, | |
| 403 href: href, | |
| 404 title: title, | |
| 405 onclick: onclick, | |
| 406 onmouseover: onmouseover, | |
| 407 onmouseout: onmouseout)); | |
| 408 pendingSourceLocationsEnd = true; | |
| 409 } | |
| 410 currentLine.annotations.addAll(annotations); | |
| 411 if (annotations.last == null) { | 372 if (annotations.last == null) { |
| 412 endCurrentLocation(); | 373 endCurrentLocation(); |
| 413 } | 374 } |
| 414 } | 375 } |
| 415 | 376 |
| 416 Map<int, List<Annotation>> annotationMap = <int, List<Annotation>>{}; | 377 Map<int, List<Annotation>> annotationMap = <int, List<Annotation>>{}; |
| 417 for (Annotation annotation in annotations) { | 378 for (Annotation annotation in annotations) { |
| 418 annotationMap.putIfAbsent(annotation.codeOffset, () => <Annotation>[]) | 379 annotationMap.putIfAbsent(annotation.codeOffset, () => <Annotation>[]) |
| 419 .add(annotation); | 380 .add(annotation); |
| 420 } | 381 } |
| 421 | 382 |
| 422 bool first = true; | 383 bool first = true; |
| 423 for (int codeOffset in annotationMap.keys.toList()..sort()) { | 384 for (int codeOffset in annotationMap.keys.toList()..sort()) { |
| 424 List<Annotation> annotationList = annotationMap[codeOffset]; | 385 List<Annotation> annotationList = annotationMap[codeOffset]; |
| 425 addSubstring(codeOffset, isFirst: first); | 386 addSubstring(codeOffset, isFirst: first); |
| 426 insertAnnotations(annotationList); | 387 insertAnnotations(annotationList); |
| 427 first = false; | 388 first = false; |
| 428 } | 389 } |
| 429 | 390 |
| 430 addSubstring(code.length, isFirst: first, isLast: true); | 391 addSubstring(code.length, isFirst: first, isLast: true); |
| 431 endCurrentLocation(); | 392 endCurrentLocation(); |
| 432 | 393 |
| 433 int start = 0; | 394 int start = startLine ?? 0; |
| 434 int end = lines.length - 1; | 395 int end = endLine ?? lines.length - 1; |
| 435 if (windowSize != null) { | 396 if (windowSize != null) { |
| 436 start = Math.max(firstLine - windowSize, start); | 397 start = Math.max(firstLine - windowSize, start); |
| 437 end = Math.min(lastLine + windowSize, end); | 398 end = Math.min(lastLine + windowSize, end); |
| 438 } | 399 } |
| 439 return lines.sublist(start, end); | 400 return lines.sublist(start, end); |
| 440 } | 401 } |
| 441 | 402 |
| 442 /// Computes the HTML representation for a collection of JavaScript code blocks. | 403 /// Computes the HTML representation for a collection of JavaScript code blocks. |
| 443 String computeJsHtml(Iterable<SourceMapHtmlInfo> infoList) { | 404 String computeJsHtml(Iterable<SourceMapHtmlInfo> infoList) { |
| 444 | 405 |
| (...skipping 235 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 680 String dartCode = truncate(codePoint.dartCode, 50); | 641 String dartCode = truncate(codePoint.dartCode, 50); |
| 681 buffer.write('<td class="code">${dartCode}</td>'); | 642 buffer.write('<td class="code">${dartCode}</td>'); |
| 682 buffer.write('<td>${escape(codePoint.sourceLocation.shortText)}</td>'); | 643 buffer.write('<td>${escape(codePoint.sourceLocation.shortText)}</td>'); |
| 683 } | 644 } |
| 684 buffer.write('</tr>'); | 645 buffer.write('</tr>'); |
| 685 }); | 646 }); |
| 686 buffer.write('</table>'); | 647 buffer.write('</table>'); |
| 687 | 648 |
| 688 return buffer.toString(); | 649 return buffer.toString(); |
| 689 } | 650 } |
| OLD | NEW |