Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 /// Client component to display [GlobalResult]s as a web app. | |
| 6 library dart2js_info.bin.inference.client; | |
| 7 | |
| 8 import 'dart:html' hide Entry; | |
| 9 import 'dart:convert'; | |
| 10 import 'package:dart2js_info/info.dart'; | |
| 11 import 'package:charcode/charcode.dart'; | |
| 12 | |
| 13 AllInfo data; | |
| 14 main() async { | |
| 15 data = AllInfo.parseFromJson( | |
| 16 JSON.decode(await HttpRequest.getString('/data'))); | |
| 17 | |
| 18 routeByHash(); | |
| 19 window.onHashChange.listen((_) => routeByHash()); | |
| 20 } | |
| 21 | |
| 22 /// Does basic routing for the client UI. | |
| 23 routeByHash() { | |
| 24 var hash = window.location.hash; | |
| 25 if (hash.isEmpty || hash == '#' || hash == '#!') { | |
| 26 handleHomePage(); | |
| 27 } else if (hash.startsWith('#!')) { | |
| 28 handleFileView(hash.substring(2)); | |
| 29 } | |
| 30 } | |
| 31 | |
| 32 /// Renders the home screen: a list of files with results. | |
| 33 handleHomePage() { | |
| 34 var files = UrlRetriever.run(data); | |
| 35 var html = new StringBuffer()..write('<ul>'); | |
| 36 for (var file in files) { | |
| 37 html.write('<li> <a href="#!$file">$file</a></li>'); | |
| 38 } | |
| 39 html.write('</ul>'); | |
| 40 document.body.setInnerHtml('$html', treeSanitizer: NodeTreeSanitizer.trusted); | |
| 41 } | |
| 42 | |
| 43 /// Renders the results of a single file: the code with highlighting for each | |
| 44 /// send. | |
| 45 handleFileView(String path) async { | |
| 46 var contents = await HttpRequest.getString('file/$path'); | |
| 47 var visitor = new SendHighlighter(path, contents); | |
| 48 data.accept(visitor); | |
| 49 var code = '${visitor.code}'; | |
| 50 document.body.setInnerHtml(''' | |
| 51 <div class="grid"> | |
| 52 <div class="main code">$code</div> | |
| 53 <div id="selections" class="right code"></div> | |
| 54 </div> | |
| 55 ''', | |
| 56 treeSanitizer: NodeTreeSanitizer.trusted); | |
| 57 | |
| 58 var div = document.querySelector('#selections'); | |
| 59 visitAllMetrics((metric, _) { | |
| 60 if (metric is GroupedMetric || metric.name == 'reachable functions') return; | |
| 61 var cssClassName = _classNameForMetric(metric); | |
| 62 var node = new Element.html('<div>' | |
| 63 '<span class="send $cssClassName inactive">${metric.name}</span>' | |
| 64 '</div>'); | |
| 65 node.children[0].onClick.listen((_) { | |
| 66 document.querySelectorAll('.$cssClassName').classes.toggle('inactive'); | |
| 67 }); | |
| 68 div.append(node); | |
| 69 }); | |
| 70 } | |
| 71 | |
| 72 /// Extracts urls for all files mentioned in the results. | |
| 73 class UrlRetriever extends RecursiveInfoVisitor { | |
| 74 List<String> _paths = []; | |
| 75 | |
| 76 static List<String> run(AllInfo results) { | |
| 77 var visitor = new UrlRetriever(); | |
| 78 results.accept(visitor); | |
| 79 return visitor._paths; | |
| 80 } | |
| 81 | |
| 82 @override | |
| 83 visitLibrary(LibraryInfo info) { | |
| 84 _paths.add(info.uri.path); | |
| 85 super.visitLibrary(info); | |
| 86 } | |
| 87 | |
| 88 @override | |
| 89 visitFunction(FunctionInfo info) { | |
| 90 var path = info.measurements?.uri?.path; | |
| 91 if (path != null) _paths.add(path); | |
| 92 } | |
| 93 } | |
| 94 | |
| 95 /// Visitors that highlights every send in the text of a file using HTML | |
| 96 /// `<span>` tags. | |
| 97 class SendHighlighter extends RecursiveInfoVisitor { | |
| 98 final String path; | |
| 99 final StringEditBuffer code; | |
| 100 | |
| 101 SendHighlighter(this.path, String contents) | |
| 102 : code = new StringEditBuffer(contents) { | |
| 103 code.insert(0, '<span class="line">'); | |
| 104 for (int i = 0; i < contents.length; i++) { | |
| 105 if (contents.codeUnitAt(i) == $lt) { | |
| 106 code.replace(i, i + 1, '<'); | |
| 107 } else if (contents.codeUnitAt(i) == $gt) { | |
| 108 code.replace(i, i + 1, '>'); | |
| 109 } else if (contents.codeUnitAt(i) == $lf) { | |
| 110 code.insert(i + 1, '</span><span class="line">'); | |
| 111 } | |
| 112 } | |
| 113 code.insert(contents.length, '</span>'); | |
| 114 } | |
| 115 | |
| 116 @override | |
| 117 visitFunction(FunctionInfo function) { | |
| 118 if (function.measurements?.uri?.path != path) return; | |
| 119 var entries = function.measurements.entries; | |
| 120 for (var metric in entries.keys) { | |
| 121 if (metric is GroupedMetric) continue; | |
| 122 var cssClassName = _classNameForMetric(metric); | |
| 123 for (var entry in entries[metric]) { | |
| 124 code.insert(entry.begin, | |
| 125 '<span class="send ${cssClassName} inactive">', -entry.end); | |
| 126 code.insert(entry.end, '</span>'); | |
| 127 } | |
| 128 } | |
| 129 } | |
| 130 } | |
| 131 | |
| 132 _classNameForMetric(Metric metric) => metric.name.replaceAll(' ', '-'); | |
| 133 | |
| 134 /// A buffer meant to apply edits on a string (rather than building a string | |
| 135 /// from scratch). Each change is described using the location information on | |
| 136 /// the original string. Internally this buffer keeps track of how a | |
| 137 /// modification in one portion can offset a modification further down the | |
| 138 /// string. | |
| 139 class StringEditBuffer { | |
|
Johnni Winther
2015/10/02 10:09:49
This should have its own package!
Siggi Cherem (dart-lang)
2015/10/02 17:16:17
:) - I moved it to it's own library for now and ad
| |
| 140 final String original; | |
| 141 final _edits = <_StringEdit>[]; | |
| 142 | |
| 143 StringEditBuffer(this.original); | |
| 144 | |
| 145 bool get hasEdits => _edits.length > 0; | |
| 146 | |
| 147 /// Edit the original text, replacing text on the range [begin] and | |
| 148 /// exclusive [end] with the [replacement] string. | |
| 149 void replace(int begin, int end, String replacement, [int sortId]) { | |
| 150 _edits.add(new _StringEdit(begin, end, replacement, sortId)); | |
| 151 } | |
| 152 | |
| 153 /// Insert [string] at [offset]. | |
| 154 /// Equivalent to `replace(offset, offset, string)`. | |
| 155 void insert(int offset, String string, [sortId]) => | |
| 156 replace(offset, offset, string, sortId); | |
| 157 | |
| 158 /// Remove text from the range [begin] to exclusive [end]. | |
| 159 /// Equivalent to `replace(begin, end, '')`. | |
| 160 void remove(int begin, int end) => replace(begin, end, ''); | |
| 161 | |
| 162 /// Applies all pending [edit]s and returns a new string. | |
| 163 /// | |
| 164 /// This method is non-destructive: it does not discard existing edits or | |
| 165 /// change the [original] string. Further edits can be added and this method | |
| 166 /// can be called again. | |
| 167 /// | |
| 168 /// Throws [UnsupportedError] if the edits were overlapping. If no edits were | |
| 169 /// made, the original string will be returned. | |
| 170 String toString() { | |
| 171 var sb = new StringBuffer(); | |
| 172 if (_edits.length == 0) return original; | |
| 173 | |
| 174 // Sort edits by start location. | |
| 175 _edits.sort(); | |
| 176 | |
| 177 int consumed = 0; | |
| 178 for (var edit in _edits) { | |
| 179 if (consumed > edit.begin) { | |
| 180 sb = new StringBuffer(); | |
| 181 sb.write('overlapping edits. Insert at offset '); | |
| 182 sb.write(edit.begin); | |
| 183 sb.write(' but have consumed '); | |
| 184 sb.write(consumed); | |
| 185 sb.write(' input characters. List of edits:'); | |
| 186 for (var e in _edits) { | |
| 187 sb.write('\n '); | |
| 188 sb.write(e); | |
| 189 } | |
| 190 throw new UnsupportedError(sb.toString()); | |
| 191 } | |
| 192 | |
| 193 // Add characters from the original string between this edit and the last | |
| 194 // one, if any. | |
| 195 var betweenEdits = original.substring(consumed, edit.begin); | |
| 196 sb.write(betweenEdits); | |
| 197 sb.write(edit.string); | |
| 198 consumed = edit.end; | |
| 199 } | |
| 200 | |
| 201 // Add any text from the end of the original string that was not replaced. | |
| 202 sb.write(original.substring(consumed)); | |
| 203 return sb.toString(); | |
| 204 } | |
| 205 } | |
| 206 | |
| 207 /// A single edit in a [StringEditBuffer]. | |
| 208 class _StringEdit implements Comparable<_StringEdit> { | |
| 209 // Offset where edit begins | |
| 210 final int begin; | |
| 211 | |
| 212 // Offset where edit ends | |
| 213 final int end; | |
| 214 | |
| 215 // Sort index as a tie-breaker for edits that have the same location. | |
| 216 final int sortId; | |
| 217 | |
| 218 // String to insert | |
| 219 final String string; | |
| 220 | |
| 221 _StringEdit(int begin, this.end, this.string, [int sortId]) | |
| 222 : begin = begin, sortId = sortId == null ? begin : sortId; | |
| 223 | |
| 224 int get length => end - begin; | |
| 225 | |
| 226 String toString() => '(Edit @ $begin,$end: "$string")'; | |
| 227 | |
| 228 int compareTo(_StringEdit other) { | |
| 229 int diff = begin - other.begin; | |
| 230 if (diff != 0) return diff; | |
| 231 diff = end - other.end; | |
| 232 if (diff != 0) return diff; | |
| 233 // use edit order as a tie breaker | |
| 234 return sortId - other.sortId; | |
| 235 } | |
| 236 } | |
| OLD | NEW |