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 |