Chromium Code Reviews| Index: runtime/observatory/lib/src/elements/script_inset.dart |
| diff --git a/runtime/observatory/lib/src/elements/script_inset.dart b/runtime/observatory/lib/src/elements/script_inset.dart |
| index aef3fe3dde57d321ee2f8a107482a897802cf8a6..d5c4625231618cf147630933054c054794d656a9 100644 |
| --- a/runtime/observatory/lib/src/elements/script_inset.dart |
| +++ b/runtime/observatory/lib/src/elements/script_inset.dart |
| @@ -9,6 +9,22 @@ import 'observatory_element.dart'; |
| import 'package:observatory/service.dart'; |
| import 'package:polymer/polymer.dart'; |
| +const nbsp = "\u00A0"; |
| + |
| +class Annotation { |
| + int line; |
| + int columnStart; |
| + int columnStop; |
| + String title; |
| + |
| + void applyStyleTo(element) { |
| + element.style.color = "blue"; |
| + element.style.textDecoration = "underline"; |
| + element.title = title; |
| + print("Annotated ${element.text} as $title"); |
|
Cutch
2015/02/27 14:44:33
Remove before committing.
rmacnak
2015/02/27 20:55:34
Done.
|
| + } |
| +} |
| + |
| /// Box with script source code in it. |
| @CustomTag('script-inset') |
| class ScriptInsetElement extends ObservatoryElement { |
| @@ -28,91 +44,98 @@ class ScriptInsetElement extends ObservatoryElement { |
| @observable int endLine; |
| @observable bool linesReady = false; |
| - // Contents are either ScriptLine or ScriptElipsis. |
| - @observable List lines = toObservable([]); |
| + var annotations = []; |
| + var annotationsCursor; |
| + |
| + StreamSubscription scriptChangeSubscription; |
| String makeLineId(int line) { |
| return 'line-$line'; |
| } |
| - String clip(String line, int start, [int limit]) { |
| - try { |
| - return line.substring(start, limit); |
| - } catch (_) { |
| - // NOTE(turnidge): Sometimes polymer updates give us garbage |
| - // starts and limits during page updates. |
| - return "OOB"; |
| - } |
| - } |
| - |
| - MutationObserver _observer; |
| - |
| void _scrollToCurrentPos() { |
| - var line = shadowRoot.querySelector('#line-$currentLine'); |
| + var line = querySelector('#${makeLineId(currentLine)}'); |
| if (line != null) { |
| line.scrollIntoView(); |
| } |
| } |
| - void _onMutation(mutations, observer) { |
| - _scrollToCurrentPos(); |
| - } |
| - |
| - void attached() { |
| - super.attached(); |
| - var table = shadowRoot.querySelector('.sourceTable'); |
| - if (table != null) { |
| - _observer = new MutationObserver(_onMutation); |
| - _observer.observe(table, childList:true); |
| - } |
| - } |
| - |
| void detached() { |
| - if (_observer != null) { |
| - _observer.disconnect(); |
| - _observer = null; |
| + if (scriptChangeSubscription != null) { |
| + // Don't leak. If only Dart and Javascript exposed weak references... |
| + scriptChangeSubscription.cancel(); |
| + scriptChangeSubscription = null; |
| } |
| super.detached(); |
| } |
| void currentPosChanged(oldValue) { |
| - _updateLines(); |
| + update(); |
| _scrollToCurrentPos(); |
| } |
| void startPosChanged(oldValue) { |
| - _updateLines(); |
| + update(); |
| } |
| void endPosChanged(oldValue) { |
| - _updateLines(); |
| + update(); |
| } |
| void scriptChanged(oldValue) { |
| - _updateLines(); |
| + update(); |
| } |
| - var _updateFuture; |
| + // Styles. |
| + Element a(String text) => new AnchorElement()..text = text; |
| + Element span(String text) => new SpanElement()..text = text; |
| + Element monospace(Element element) { |
| + element.style.fontFamily = "consolas, courier, monospace"; |
| + return element; |
| + } |
| + Element row(Element element) { |
| + element.style.display = "table-row"; |
| + return element; |
| + } |
| + Element cell(Element element) { |
| + element.style.display = "table-cell"; |
| + element.style.verticalAlign = "top"; |
| + return element; |
| + } |
| + Element hitsUnknown(Element element) { |
| + element.style.backgroundColor = ""; |
| + element.title = ""; |
| + return element; |
| + } |
| + Element hitsNotExecuted(Element element) { |
| + element.style.backgroundColor = "#e66"; |
| + element.title = "Line did not execute"; |
| + return element; |
| + } |
| + Element hitsExecuted(Element element) { |
| + element.style.backgroundColor = "#6d6"; |
| + element.title = "Line did execute"; |
| + return element; |
| + } |
| - void _updateLines() { |
| - linesReady = false; |
| - if (_updateFuture != null) { |
| - // Already scheduled. |
| - return; |
| - } |
| - if (script == null) { |
| - // Wait for script to be assigned. |
| - return; |
| - } |
| + void update() { |
| + if (script == null) return; |
|
Cutch
2015/02/27 14:44:33
curlies- if ( ... ) { return; }
should you ensure
rmacnak
2015/02/27 20:55:34
Done.
|
| if (!script.loaded) { |
| - _updateFuture = script.load().then((_) { |
| - if (script.loaded) { |
| - _updateFuture = null; |
| - _updateLines(); |
| - } |
| - }); |
| - return; |
| + return script.load().then((_) => update()); |
| + } |
| + |
| + if (scriptChangeSubscription == null) { |
| + scriptChangeSubscription = script.changes.listen((_) => update()); |
| } |
| + |
| + computeAnnotations(); |
| + |
| + var table = linesTable(); |
| + children.clear(); |
| + children.add(table); |
| + } |
| + |
| + void computeAnnotations() { |
| startLine = (startPos != null |
| ? script.tokenToLine(startPos) |
| : 1); |
| @@ -126,33 +149,100 @@ class ScriptInsetElement extends ObservatoryElement { |
| ? script.tokenToLine(endPos) |
| : script.lines.length); |
| - lines.clear(); |
| - int blankLineCount = 0; |
| - for (int i = (startLine - 1); i <= (endLine - 1); i++) { |
| - if (script.lines[i].isBlank) { |
| - // Try to introduce elipses if there are 4 or more contiguous blank lines. |
| - blankLineCount++; |
| - } else { |
| - if (blankLineCount > 0) { |
| - int firstBlank = i - blankLineCount; |
| - int lastBlank = i - 1; |
| - if (blankLineCount < 4) { |
| - // Too few blank lines for an elipsis. |
| - for (int j = firstBlank; j <= lastBlank; j++) { |
| - lines.add(script.lines[j]); |
| - } |
| - } else { |
| - // Add an elipsis for the skipped region. |
| - lines.add(script.lines[firstBlank]); |
| - lines.add(null); |
| - lines.add(script.lines[lastBlank]); |
| - } |
| - blankLineCount = 0; |
| - } |
| - lines.add(script.lines[i]); |
| + annotations.clear(); |
| + if (currentLine != null) { |
| + var a = new Annotation(); |
| + a.line = currentLine; |
| + a.columnStart = currentCol; |
| + a.columnStop = currentCol + 1; |
| + a.title = "Current invocation"; |
| + annotations.add(a); |
| + } |
| + |
| + // TODO(rmacnak): Call site data. |
| + } |
| + |
| + Element linesTable() { |
| + var table = new DivElement(); |
| + table.style.fontFamily = "monospace"; |
|
Cutch
2015/02/27 14:44:33
use css classes defined in html instead of setting
rmacnak
2015/02/27 20:55:34
Done.
|
| + table.style.fontWeight = "400"; |
| + table.style.display = "table"; |
| + table.style.backgroundColor = "#f5f5f5"; |
| + table.style.border = "1px solid #ccc"; |
| + table.style.padding = "10px"; |
| + table.style.overflowY = "auto"; |
| + table.style.width = "100%"; |
| + |
| + int lineNumber = 1; |
| + annotationsCursor = 0; |
| + for (ScriptLine line in script.lines) { |
| + if (startLine <= lineNumber && lineNumber <= endLine) { |
| + table.append(lineElement(line)); |
| } |
| + lineNumber++; |
| } |
| - linesReady = true; |
| + |
| + return table; |
| + } |
| + |
| + // Assumes annotations are sorted. |
| + Annotation nextAnnotationOnLine(int line) { |
| + if (annotationsCursor >= annotations.length) return null; |
| + var annotation = annotations[annotationsCursor]; |
| + if (annotation.line != line) return null; |
| + annotationsCursor++; |
| + return annotation; |
| + } |
| + |
| + Element lineElement(ScriptLine line) { |
| + return row(new DivElement()) |
| + ..append(cell(lineBreakpointElement(line))) |
| + ..append(cell(lineNumberElement(line))) |
| + ..append(cell(lineSourceElement(line))); |
| + } |
| + |
| + Element lineBreakpointElement(ScriptLine line) { |
| + return new Element.tag("breakpoint-toggle") |
| + ..line = line; |
| + } |
| + |
| + Element lineNumberElement(ScriptLine line) { |
| + var lineNumber = line.line; |
| + var node = monospace(span("$nbsp$lineNumber$nbsp")); |
| + node.style.textAlign = "right"; |
| + node.style.color = "#a8a8a8"; |
| + |
| + if (line.hits == null) hitsUnknown(node); |
|
Cutch
2015/02/27 14:44:33
braces and new lines please
rmacnak
2015/02/27 20:55:34
Done.
|
| + else if (line.hits == 0) hitsNotExecuted(node); |
| + else hitsExecuted(node); |
| + |
| + return node; |
| + } |
| + |
| + Element lineSourceElement(ScriptLine line) { |
| + var node = new DivElement(); |
| + node.style.whiteSpace = "pre"; |
| + node.id = makeLineId(line.line); |
| + |
| + var position = 0; |
| + consumeUntil(var stop) { |
| + if (stop <= position) return; // Empty gap between annotations/boundries. |
| + var chunk = line.text.substring(position, stop); |
| + var chunkNode = monospace(span(chunk)); |
| + node.append(chunkNode); |
| + position = stop; |
| + return chunkNode; |
| + } |
| + |
| + // TODO(rmacnak): Tolerate overlapping annotations. |
| + var annotation; |
| + while ((annotation = nextAnnotationOnLine(line.line)) != null) { |
| + consumeUntil(annotation.columnStart); |
| + annotation.applyStyleTo(consumeUntil(annotation.columnStop)); |
| + } |
| + consumeUntil(line.text.length); |
| + |
| + return node; |
| } |
| ScriptInsetElement.created() : super.created(); |