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 |