Index: runtime/observatory/lib/src/elements/debugger.dart |
diff --git a/runtime/observatory/lib/src/elements/debugger.dart b/runtime/observatory/lib/src/elements/debugger.dart |
index 7b9bc053afd758eb68d056b57e69fc6e6e41e3b7..a11f046e66d5616d4b669b5be9bcde23239f5564 100644 |
--- a/runtime/observatory/lib/src/elements/debugger.dart |
+++ b/runtime/observatory/lib/src/elements/debugger.dart |
@@ -854,6 +854,67 @@ class RefreshCommand extends DebuggerCommand { |
'Syntax: refresh <subcommand>\n'; |
} |
+class _VMStreamPrinter { |
+ ObservatoryDebugger _debugger; |
+ |
+ _VMStreamPrinter(this._debugger); |
+ |
+ String _savedStream; |
+ String _savedIsolate; |
+ String _savedLine; |
+ List<String> _buffer = []; |
+ |
+ void onEvent(String streamName, ServiceEvent event) { |
+ String isolateName = event.isolate.name; |
+ // If we get a line from a different isolate/stream, flush |
+ // any pending output, even if it is not newline-terminated. |
+ if ((_savedIsolate != null && isolateName != _savedIsolate) || |
+ (_savedStream != null && streamName != _savedStream)) { |
+ flush(); |
+ } |
+ String data = event.bytesAsString; |
+ bool hasNewline = data.endsWith('\n'); |
+ if (_savedLine != null) { |
+ data = _savedLine + data; |
+ _savedIsolate = null; |
+ _savedStream = null; |
+ _savedLine = null; |
+ } |
+ var lines = data.split('\n').where((line) => line != '').toList(); |
+ if (lines.isEmpty) { |
+ return; |
+ } |
+ int limit = (hasNewline ? lines.length : lines.length - 1); |
+ for (int i = 0; i < limit; i++) { |
+ _buffer.add(_format(isolateName, streamName, lines[i])); |
+ } |
+ // If there is no newline, we save the last line of output for next time. |
+ if (!hasNewline) { |
+ _savedIsolate = isolateName; |
+ _savedStream = streamName; |
+ _savedLine = lines[lines.length - 1]; |
+ } |
+ } |
+ |
+ void flush() { |
+ // If there is any saved output, flush it now. |
+ if (_savedLine != null) { |
+ _buffer.add(_format(_savedIsolate, _savedStream, _savedLine)); |
+ _savedIsolate = null; |
+ _savedStream = null; |
+ _savedLine = null; |
+ } |
+ if (_buffer.isNotEmpty) { |
+ _debugger.console.printStdio(_buffer); |
+ _buffer.clear(); |
+ } |
+ } |
+ |
+ String _format(String isolateName, String streamName, String line) { |
+ return '${isolateName}:${streamName}> ${line}'; |
+ } |
+} |
+ |
// Tracks the state for an isolate debugging session. |
class ObservatoryDebugger extends Debugger { |
RootCommand cmd; |
@@ -898,6 +959,7 @@ class ObservatoryDebugger extends Debugger { |
new IsolateCommand(this), |
new RefreshCommand(this), |
]); |
+ _stdioPrinter = new _VMStreamPrinter(this); |
} |
VM get vm => page.app.vm; |
@@ -997,6 +1059,7 @@ class ObservatoryDebugger extends Debugger { |
} |
void reportStatus() { |
+ flushStdio(); |
if (_isolate == null) { |
console.print('No current isolate'); |
} else if (_isolate.idle) { |
@@ -1116,6 +1179,7 @@ class ObservatoryDebugger extends Debugger { |
case ServiceEvent.kPauseException: |
if (event.owner == isolate) { |
_refreshStack(event).then((_) { |
+ flushStdio(); |
_reportPause(event); |
}); |
} |
@@ -1123,6 +1187,7 @@ class ObservatoryDebugger extends Debugger { |
case ServiceEvent.kResume: |
if (event.owner == isolate) { |
+ flushStdio(); |
console.print('Continuing...'); |
} |
break; |
@@ -1147,6 +1212,20 @@ class ObservatoryDebugger extends Debugger { |
} |
} |
+ _VMStreamPrinter _stdioPrinter; |
+ |
+ void flushStdio() { |
+ _stdioPrinter.flush(); |
+ } |
+ |
+ void onStdout(ServiceEvent event) { |
+ _stdioPrinter.onEvent('stdout', event); |
+ } |
+ |
+ void onStderr(ServiceEvent event) { |
+ _stdioPrinter.onEvent('stderr', event); |
+ } |
+ |
static String _commonPrefix(String a, String b) { |
int pos = 0; |
while (pos < a.length && pos < b.length) { |
@@ -1236,6 +1315,8 @@ class DebuggerPageElement extends ObservatoryElement { |
Future<StreamSubscription> _isolateSubscriptionFuture; |
Future<StreamSubscription> _debugSubscriptionFuture; |
+ Future<StreamSubscription> _stdoutSubscriptionFuture; |
+ Future<StreamSubscription> _stderrSubscriptionFuture; |
@override |
void attached() { |
@@ -1269,6 +1350,13 @@ class DebuggerPageElement extends ObservatoryElement { |
app.vm.listenEventStream(VM.kIsolateStream, debugger.onEvent); |
_debugSubscriptionFuture = |
app.vm.listenEventStream(VM.kDebugStream, debugger.onEvent); |
+ _stdoutSubscriptionFuture = |
+ app.vm.listenEventStream(VM.kStdoutStream, debugger.onStdout); |
+ _stderrSubscriptionFuture = |
+ app.vm.listenEventStream(VM.kStderrStream, debugger.onStderr); |
+ |
+ // Turn on the periodic poll timer for this page. |
+ pollPeriod = const Duration(milliseconds:100); |
onClick.listen((event) { |
// Random clicks should focus on the text box. If the user selects |
@@ -1280,12 +1368,20 @@ class DebuggerPageElement extends ObservatoryElement { |
}); |
} |
+ void onPoll() { |
+ debugger.flushStdio(); |
+ } |
+ |
@override |
void detached() { |
cancelFutureSubscription(_isolateSubscriptionFuture); |
_isolateSubscriptionFuture = null; |
cancelFutureSubscription(_debugSubscriptionFuture); |
_debugSubscriptionFuture = null; |
+ cancelFutureSubscription(_stdoutSubscriptionFuture); |
+ _stdoutSubscriptionFuture = null; |
+ cancelFutureSubscription(_stderrSubscriptionFuture); |
+ _stderrSubscriptionFuture = null; |
super.detached(); |
} |
} |
@@ -1665,6 +1761,21 @@ class DebuggerConsoleElement extends ObservatoryElement { |
span.scrollIntoView(); |
} |
+ void printStdio(List<String> lines) { |
+ var lastSpan; |
+ for (var line in lines) { |
+ var span = new SpanElement(); |
+ span.classes.add('green'); |
+ span.appendText(line); |
+ span.appendText('\n'); |
+ $['consoleText'].children.add(span); |
+ lastSpan = span; |
+ } |
+ if (lastSpan != null) { |
+ lastSpan.scrollIntoView(); |
+ } |
+ } |
+ |
void printRef(Instance ref, { bool newline:true }) { |
var refElement = new Element.tag('instance-ref'); |
refElement.ref = ref; |