Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, 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 library script_inset_element; | 5 library script_inset_element; |
| 6 | 6 |
| 7 import 'dart:async'; | |
| 7 import 'dart:html'; | 8 import 'dart:html'; |
| 8 import 'observatory_element.dart'; | 9 import 'observatory_element.dart'; |
| 9 import 'package:observatory/service.dart'; | 10 import 'package:observatory/service.dart'; |
| 10 import 'package:polymer/polymer.dart'; | 11 import 'package:polymer/polymer.dart'; |
| 11 | 12 |
| 13 const nbsp = "\u00A0"; | |
| 14 | |
| 15 class Annotation { | |
| 16 int line; | |
| 17 int columnStart; | |
| 18 int columnStop; | |
| 19 String title; | |
| 20 | |
| 21 void applyStyleTo(element) { | |
| 22 element.style.color = "blue"; | |
| 23 element.style.textDecoration = "underline"; | |
| 24 element.title = title; | |
| 25 } | |
| 26 } | |
| 27 | |
| 12 /// Box with script source code in it. | 28 /// Box with script source code in it. |
| 13 @CustomTag('script-inset') | 29 @CustomTag('script-inset') |
| 14 class ScriptInsetElement extends ObservatoryElement { | 30 class ScriptInsetElement extends ObservatoryElement { |
| 15 @published Script script; | 31 @published Script script; |
| 16 | 32 |
| 17 /// Set the height to make the script inset scroll. Otherwise it | 33 /// Set the height to make the script inset scroll. Otherwise it |
| 18 /// will show from startPos to endPos. | 34 /// will show from startPos to endPos. |
| 19 @published String height = null; | 35 @published String height = null; |
| 20 | 36 |
| 21 @published int currentPos; | 37 @published int currentPos; |
| 22 @published int startPos; | 38 @published int startPos; |
| 23 @published int endPos; | 39 @published int endPos; |
| 24 | 40 |
| 25 @observable int currentLine; | 41 @observable int currentLine; |
| 26 @observable int currentCol; | 42 @observable int currentCol; |
| 27 @observable int startLine; | 43 @observable int startLine; |
| 28 @observable int endLine; | 44 @observable int endLine; |
| 29 @observable bool linesReady = false; | |
| 30 | 45 |
| 31 // Contents are either ScriptLine or ScriptElipsis. | 46 var annotations = []; |
| 32 @observable List lines = toObservable([]); | 47 var annotationsCursor; |
| 48 | |
| 49 StreamSubscription scriptChangeSubscription; | |
| 33 | 50 |
| 34 String makeLineId(int line) { | 51 String makeLineId(int line) { |
| 35 return 'line-$line'; | 52 return 'line-$line'; |
| 36 } | 53 } |
| 37 | 54 |
| 38 String clip(String line, int start, [int limit]) { | |
| 39 try { | |
| 40 return line.substring(start, limit); | |
| 41 } catch (_) { | |
| 42 // NOTE(turnidge): Sometimes polymer updates give us garbage | |
| 43 // starts and limits during page updates. | |
| 44 return "OOB"; | |
| 45 } | |
| 46 } | |
| 47 | |
| 48 MutationObserver _observer; | |
| 49 | |
| 50 void _scrollToCurrentPos() { | 55 void _scrollToCurrentPos() { |
| 51 var line = shadowRoot.querySelector('#line-$currentLine'); | 56 var line = querySelector('#${makeLineId(currentLine)}'); |
| 52 if (line != null) { | 57 if (line != null) { |
| 53 line.scrollIntoView(); | 58 line.scrollIntoView(); |
| 54 } | 59 } |
| 55 } | 60 } |
| 56 | 61 |
| 57 void _onMutation(mutations, observer) { | |
| 58 _scrollToCurrentPos(); | |
| 59 } | |
| 60 | |
| 61 void attached() { | |
| 62 super.attached(); | |
| 63 var table = shadowRoot.querySelector('.sourceTable'); | |
| 64 if (table != null) { | |
| 65 _observer = new MutationObserver(_onMutation); | |
| 66 _observer.observe(table, childList:true); | |
| 67 } | |
| 68 } | |
| 69 | |
| 70 void detached() { | 62 void detached() { |
| 71 if (_observer != null) { | 63 if (scriptChangeSubscription != null) { |
| 72 _observer.disconnect(); | 64 // Don't leak. If only Dart and Javascript exposed weak references... |
| 73 _observer = null; | 65 scriptChangeSubscription.cancel(); |
| 66 scriptChangeSubscription = null; | |
| 74 } | 67 } |
| 75 super.detached(); | 68 super.detached(); |
| 76 } | 69 } |
| 77 | 70 |
| 78 void currentPosChanged(oldValue) { | 71 void currentPosChanged(oldValue) { |
| 79 _updateLines(); | 72 update(); |
| 80 _scrollToCurrentPos(); | 73 _scrollToCurrentPos(); |
| 81 } | 74 } |
| 82 | 75 |
| 83 void startPosChanged(oldValue) { | 76 void startPosChanged(oldValue) { |
| 84 _updateLines(); | 77 update(); |
| 85 } | 78 } |
| 86 | 79 |
| 87 void endPosChanged(oldValue) { | 80 void endPosChanged(oldValue) { |
| 88 _updateLines(); | 81 update(); |
| 89 } | 82 } |
| 90 | 83 |
| 91 void scriptChanged(oldValue) { | 84 void scriptChanged(oldValue) { |
| 92 _updateLines(); | 85 update(); |
| 93 } | 86 } |
| 94 | 87 |
| 95 var _updateFuture; | 88 Element a(String text) => new AnchorElement()..text = text; |
| 89 Element span(String text) => new SpanElement()..text = text; | |
| 96 | 90 |
| 97 void _updateLines() { | 91 Element hitsUnknown(Element element) { |
| 98 linesReady = false; | 92 element.classes.add('hitsNone'); |
| 99 if (_updateFuture != null) { | 93 element.title = ""; |
| 100 // Already scheduled. | 94 return element; |
| 101 return; | 95 } |
| 102 } | 96 Element hitsNotExecuted(Element element) { |
| 97 element.classes.add('hitsNotExecuted'); | |
| 98 element.title = "Line did not execute"; | |
| 99 return element; | |
| 100 } | |
| 101 Element hitsExecuted(Element element) { | |
| 102 element.classes.add('hitsExecuted'); | |
| 103 element.title = "Line did execute"; | |
| 104 return element; | |
| 105 } | |
| 106 | |
| 107 Element container; | |
| 108 | |
| 109 void update() { | |
| 103 if (script == null) { | 110 if (script == null) { |
| 104 // Wait for script to be assigned. | 111 // We may have previously had a script. |
| 112 if (container != null) { | |
| 113 container.children.clear(); | |
| 114 } | |
| 105 return; | 115 return; |
| 106 } | 116 } |
| 107 if (!script.loaded) { | 117 if (!script.loaded) { |
| 108 _updateFuture = script.load().then((_) { | 118 script.load().then((_) => update()); |
| 109 if (script.loaded) { | |
| 110 _updateFuture = null; | |
| 111 _updateLines(); | |
| 112 } | |
| 113 }); | |
| 114 return; | 119 return; |
| 115 } | 120 } |
| 121 | |
| 122 if (scriptChangeSubscription == null) { | |
| 123 scriptChangeSubscription = script.changes.listen((_) => update()); | |
| 124 } | |
| 125 | |
| 126 computeAnnotations(); | |
| 127 | |
| 128 var table = linesTable(); | |
| 129 if (container == null) { | |
| 130 // Indirect to avoid deleting the style element. | |
| 131 container = new DivElement(); | |
| 132 shadowRoot.append(container); | |
| 133 } | |
| 134 container.children.clear(); | |
| 135 container.children.add(table); | |
| 136 } | |
| 137 | |
| 138 void computeAnnotations() { | |
| 116 startLine = (startPos != null | 139 startLine = (startPos != null |
| 117 ? script.tokenToLine(startPos) | 140 ? script.tokenToLine(startPos) |
| 118 : 1); | 141 : 1); |
| 119 currentLine = (currentPos != null | 142 currentLine = (currentPos != null |
| 120 ? script.tokenToLine(currentPos) | 143 ? script.tokenToLine(currentPos) |
| 121 : null); | 144 : null); |
| 122 currentCol = (currentPos != null | 145 currentCol = (currentPos != null |
| 123 ? (script.tokenToCol(currentPos) - 1) // make this 0-based. | 146 ? (script.tokenToCol(currentPos) - 1) // make this 0-based. |
| 124 : null); | 147 : null); |
| 125 endLine = (endPos != null | 148 endLine = (endPos != null |
| 126 ? script.tokenToLine(endPos) | 149 ? script.tokenToLine(endPos) |
| 127 : script.lines.length); | 150 : script.lines.length); |
| 128 | 151 |
| 129 lines.clear(); | 152 annotations.clear(); |
| 153 if (currentLine != null) { | |
| 154 var a = new Annotation(); | |
| 155 a.line = currentLine; | |
| 156 a.columnStart = currentCol; | |
| 157 a.columnStop = currentCol + 1; | |
| 158 a.title = "Point of interest"; | |
| 159 annotations.add(a); | |
| 160 } | |
| 161 | |
| 162 // TODO(rmacnak): Call site data. | |
| 163 } | |
| 164 | |
| 165 Element linesTable() { | |
| 166 var table = new DivElement(); | |
| 167 table.classes.add("sourceTable"); | |
| 168 | |
| 169 annotationsCursor = 0; | |
| 170 | |
| 130 int blankLineCount = 0; | 171 int blankLineCount = 0; |
| 131 for (int i = (startLine - 1); i <= (endLine - 1); i++) { | 172 for (int i = (startLine - 1); i <= (endLine - 1); i++) { |
| 132 if (script.lines[i].isBlank) { | 173 if (script.lines[i].isBlank) { |
| 133 // Try to introduce elipses if there are 4 or more contiguous blank line s. | 174 // Try to introduce elipses if there are 4 or more contiguous |
| 175 // blank lines. | |
| 134 blankLineCount++; | 176 blankLineCount++; |
| 135 } else { | 177 } else { |
| 136 if (blankLineCount > 0) { | 178 if (blankLineCount > 0) { |
| 137 int firstBlank = i - blankLineCount; | 179 int firstBlank = i - blankLineCount; |
| 138 int lastBlank = i - 1; | 180 int lastBlank = i - 1; |
| 139 if (blankLineCount < 4) { | 181 if (blankLineCount < 4) { |
| 140 // Too few blank lines for an elipsis. | 182 // Too few blank lines for an elipsis. |
| 141 for (int j = firstBlank; j <= lastBlank; j++) { | 183 for (int j = firstBlank; j <= lastBlank; j++) { |
| 142 lines.add(script.lines[j]); | 184 table.append(lineElement(script.lines[j])); |
| 143 } | 185 } |
| 144 } else { | 186 } else { |
| 145 // Add an elipsis for the skipped region. | 187 // Add an elipsis for the skipped region. |
| 146 lines.add(script.lines[firstBlank]); | 188 table.append(lineElement(script.lines[firstBlank])); |
| 147 lines.add(null); | 189 table.append(lineElement(null)); |
| 148 lines.add(script.lines[lastBlank]); | 190 table.append(lineElement(script.lines[lastBlank])); |
| 149 } | 191 } |
| 150 blankLineCount = 0; | 192 blankLineCount = 0; |
| 151 } | 193 } |
| 152 lines.add(script.lines[i]); | 194 table.append(lineElement(script.lines[i])); |
| 153 } | 195 } |
| 154 } | 196 } |
| 155 linesReady = true; | 197 |
| 198 return table; | |
| 199 } | |
| 200 | |
| 201 // Assumes annotations are sorted. | |
| 202 Annotation nextAnnotationOnLine(int line) { | |
| 203 if (annotationsCursor >= annotations.length) return null; | |
| 204 var annotation = annotations[annotationsCursor]; | |
| 205 if (annotation.line != line) return null; | |
| 206 annotationsCursor++; | |
| 207 return annotation; | |
| 208 } | |
| 209 | |
| 210 Element lineElement(ScriptLine line) { | |
| 211 var e = new DivElement(); | |
| 212 e.classes.add("sourceRow"); | |
| 213 e.append(lineBreakpointElement(line)); | |
| 214 e.append(lineNumberElement(line)); | |
| 215 e.append(lineSourceElement(line)); | |
| 216 return e; | |
| 217 } | |
| 218 | |
| 219 Element lineBreakpointElement(ScriptLine line) { | |
| 220 BreakpointToggleElement e = new Element.tag("breakpoint-toggle"); | |
| 221 e.line = line; | |
| 222 return e; | |
| 223 } | |
| 224 | |
| 225 Element lineNumberElement(ScriptLine line) { | |
| 226 var lineNumber = line == null ? "..." : line.line; | |
| 227 var e = span("$nbsp$lineNumber$nbsp"); | |
| 228 | |
| 229 if ((line == null) || (line.hits == null)) { | |
| 230 hitsUnknown(e); | |
| 231 } else if (line.hits == 0) { | |
| 232 hitsNotExecuted(e); | |
| 233 } else { | |
| 234 hitsExecuted(e); | |
| 235 } | |
| 236 | |
| 237 return e; | |
| 238 } | |
| 239 | |
| 240 Element lineSourceElement(ScriptLine line) { | |
| 241 var e = new DivElement(); | |
| 242 e.classes.add("sourceItem"); | |
| 243 | |
| 244 if (line != null) { | |
| 245 if (line.line == currentLine) { | |
| 246 e.classes.add("sourceItemCurrent"); | |
| 247 } | |
| 248 | |
| 249 e.id = makeLineId(line.line); | |
| 250 | |
| 251 var position = 0; | |
| 252 consumeUntil(var stop) { | |
| 253 if (stop <= position) return; // Empty gap between annotations/boundrie s. | |
|
Cutch
2015/03/02 19:11:44
column limit
| |
| 254 var chunk = line.text.substring(position, stop); | |
| 255 var chunkNode = span(chunk); | |
| 256 e.append(chunkNode); | |
| 257 position = stop; | |
| 258 return chunkNode; | |
| 259 } | |
| 260 | |
| 261 // TODO(rmacnak): Tolerate overlapping annotations. | |
| 262 var annotation; | |
| 263 while ((annotation = nextAnnotationOnLine(line.line)) != null) { | |
| 264 consumeUntil(annotation.columnStart); | |
| 265 annotation.applyStyleTo(consumeUntil(annotation.columnStop)); | |
| 266 } | |
| 267 consumeUntil(line.text.length); | |
| 268 } | |
| 269 | |
| 270 return e; | |
| 156 } | 271 } |
| 157 | 272 |
| 158 ScriptInsetElement.created() : super.created(); | 273 ScriptInsetElement.created() : super.created(); |
| 159 } | 274 } |
| 160 | 275 |
| 161 @CustomTag('breakpoint-toggle') | 276 @CustomTag('breakpoint-toggle') |
| 162 class BreakpointToggleElement extends ObservatoryElement { | 277 class BreakpointToggleElement extends ObservatoryElement { |
| 163 @published ScriptLine line; | 278 @published ScriptLine line; |
| 164 @observable bool busy = false; | 279 @observable bool busy = false; |
| 165 | 280 |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 176 } else { | 291 } else { |
| 177 // Existing breakpoint. Remove it. | 292 // Existing breakpoint. Remove it. |
| 178 line.script.isolate.removeBreakpoint(line.bpt).then((_) { | 293 line.script.isolate.removeBreakpoint(line.bpt).then((_) { |
| 179 busy = false; | 294 busy = false; |
| 180 }); | 295 }); |
| 181 } | 296 } |
| 182 } | 297 } |
| 183 | 298 |
| 184 BreakpointToggleElement.created() : super.created(); | 299 BreakpointToggleElement.created() : super.created(); |
| 185 } | 300 } |
| OLD | NEW |