Index: bin/inference/client.dart |
diff --git a/bin/inference/client.dart b/bin/inference/client.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..7f7fcbd63b421c6b16dd131cf10b55c4ab351f3a |
--- /dev/null |
+++ b/bin/inference/client.dart |
@@ -0,0 +1,134 @@ |
+// Copyright (c) 2015, 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. |
+ |
+/// Client component to display [GlobalResult]s as a web app. |
+library dart2js_info.bin.inference.client; |
+ |
+import 'dart:html' hide Entry; |
+import 'dart:convert'; |
+import 'package:dart2js_info/info.dart'; |
+import 'package:dart2js_info/src/string_edit_buffer.dart'; |
+import 'package:charcode/charcode.dart'; |
+ |
+AllInfo data; |
+main() async { |
+ data = |
+ AllInfo.parseFromJson(JSON.decode(await HttpRequest.getString('/data'))); |
+ |
+ routeByHash(); |
+ window.onHashChange.listen((_) => routeByHash()); |
+} |
+ |
+/// Does basic routing for the client UI. |
+routeByHash() { |
+ var hash = window.location.hash; |
+ if (hash.isEmpty || hash == '#' || hash == '#!') { |
+ handleHomePage(); |
+ } else if (hash.startsWith('#!')) { |
+ handleFileView(hash.substring(2)); |
+ } |
+} |
+ |
+/// Renders the home screen: a list of files with results. |
+handleHomePage() { |
+ var files = UrlRetriever.run(data); |
+ var html = new StringBuffer()..write('<ul>'); |
+ for (var file in files) { |
+ html.write('<li> <a href="#!$file">$file</a></li>'); |
+ } |
+ html.write('</ul>'); |
+ document.body.setInnerHtml('$html', treeSanitizer: NodeTreeSanitizer.trusted); |
+} |
+ |
+/// Renders the results of a single file: the code with highlighting for each |
+/// send. |
+handleFileView(String path) async { |
+ var contents = await HttpRequest.getString('file/$path'); |
+ var visitor = new SendHighlighter(path, contents); |
+ data.accept(visitor); |
+ var code = '${visitor.code}'; |
+ document.body.setInnerHtml( |
+ ''' |
+ <div class="grid"> |
+ <div class="main code">$code</div> |
+ <div id="selections" class="right code"></div> |
+ </div> |
+ ''', |
+ treeSanitizer: NodeTreeSanitizer.trusted); |
+ |
+ var div = document.querySelector('#selections'); |
+ visitAllMetrics((metric, _) { |
+ if (metric is GroupedMetric || metric.name == 'reachable functions') return; |
+ var cssClassName = _classNameForMetric(metric); |
+ var node = new Element.html('<div>' |
+ '<span class="send $cssClassName inactive">${metric.name}</span>' |
+ '</div>'); |
+ node.children[0].onClick.listen((_) { |
+ document.querySelectorAll('.$cssClassName').classes.toggle('inactive'); |
+ }); |
+ div.append(node); |
+ }); |
+} |
+ |
+/// Extracts urls for all files mentioned in the results. |
+class UrlRetriever extends RecursiveInfoVisitor { |
+ List<String> _paths = []; |
+ |
+ static List<String> run(AllInfo results) { |
+ var visitor = new UrlRetriever(); |
+ results.accept(visitor); |
+ return visitor._paths; |
+ } |
+ |
+ @override |
+ visitLibrary(LibraryInfo info) { |
+ _paths.add(info.uri.path); |
+ super.visitLibrary(info); |
+ } |
+ |
+ @override |
+ visitFunction(FunctionInfo info) { |
+ var path = info.measurements?.uri?.path; |
+ if (path != null) _paths.add(path); |
+ } |
+} |
+ |
+/// Visitors that highlights every send in the text of a file using HTML |
+/// `<span>` tags. |
+class SendHighlighter extends RecursiveInfoVisitor { |
+ final String path; |
+ final StringEditBuffer code; |
+ |
+ SendHighlighter(this.path, String contents) |
+ : code = new StringEditBuffer(contents) { |
+ code.insert(0, '<span class="line">'); |
+ for (int i = 0; i < contents.length; i++) { |
+ if (contents.codeUnitAt(i) == $lt) { |
+ code.replace(i, i + 1, '<'); |
+ } else if (contents.codeUnitAt(i) == $gt) { |
+ code.replace(i, i + 1, '>'); |
+ } else if (contents.codeUnitAt(i) == $lf) { |
+ code.insert(i + 1, '</span><span class="line">'); |
+ } |
+ } |
+ code.insert(contents.length, '</span>'); |
+ } |
+ |
+ @override |
+ visitFunction(FunctionInfo function) { |
+ if (function.measurements?.uri?.path != path) return; |
+ var entries = function.measurements.entries; |
+ for (var metric in entries.keys) { |
+ if (metric is GroupedMetric) continue; |
+ var cssClassName = _classNameForMetric(metric); |
+ for (var entry in entries[metric]) { |
+ code.insert(entry.begin, '<span class="send ${cssClassName} inactive">', |
+ -entry.end); |
+ code.insert(entry.end, '</span>'); |
+ } |
+ } |
+ } |
+} |
+ |
+_classNameForMetric(Metric metric) => metric.name.replaceAll(' ', '-'); |