Skip to content

Commit

Permalink
Fix error reporting (#3162)
Browse files Browse the repository at this point in the history
* Report errors separately from console logging

* Use Splitter for console

* Remove tab bar, show error output instead of console output if it exists.

* Fix layout and execution issues

* revert change to hotReloadableChannels

* Remove print statements

* Remove print statement

* Remove print statements

* remove print

* Sort imports
  • Loading branch information
johnpryan authored Mar 3, 2025
1 parent 8c497af commit 3219159
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 104 deletions.
10 changes: 10 additions & 0 deletions pkgs/dart_services/lib/src/common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import 'dart:ui_web' as ui_web;
import 'dart:js_interop';
import 'dart:js_interop_unsafe';
import 'package:flutter/foundation.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import 'generated_plugin_registrant.dart' as pluginRegistrant;
Expand All @@ -29,7 +30,16 @@ import 'main.dart' as entrypoint;
@JS('window')
external JSObject get _window;
@JS('reportFlutterError')
external void _reportFlutterError(String message);
Future<void> main() async {
// Capture errors and throw them to the JS console so DartPad can report them
// correctly.
FlutterError.onError = (details) {
_reportFlutterError(details.toString());
};
// Mock DWDS indicators to allow Flutter to register hot reload 'reassemble'
// extension.
_window[r'$dwdsVersion'] = true.toJS;
Expand Down
101 changes: 53 additions & 48 deletions pkgs/dartpad_ui/lib/console.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import 'enable_gen_ai.dart';
import 'model.dart';
import 'suggest_fix.dart';
import 'theme.dart';
import 'utils.dart';
import 'widgets.dart';

class ConsoleWidget extends StatefulWidget {
final bool showDivider;
final ValueNotifier<String> output;
final ConsoleNotifier output;

const ConsoleWidget({
this.showDivider = true,
this.showDivider = false,
required this.output,
super.key,
});
Expand Down Expand Up @@ -64,62 +65,66 @@ class _ConsoleWidgetState extends State<ConsoleWidget> {
: null,
),
padding: const EdgeInsets.all(denseSpacing),
child: ValueListenableBuilder(
valueListenable: widget.output,
builder:
(context, consoleOutput, _) => Stack(
children: [
SizedBox.expand(
child: SingleChildScrollView(
controller: scrollController,
child: SelectableText(
consoleOutput,
maxLines: null,
style: GoogleFonts.robotoMono(
fontSize: theme.textTheme.bodyMedium?.fontSize,
),
child: ListenableBuilder(
listenable: widget.output,
builder: (context, _) {
return Stack(
children: [
SizedBox.expand(
child: SingleChildScrollView(
controller: scrollController,
child: SelectableText(
widget.output.valueToDisplay,
maxLines: null,
style: GoogleFonts.robotoMono(
fontSize: theme.textTheme.bodyMedium?.fontSize,
color: switch (widget.output.hasError) {
false => theme.textTheme.bodyMedium?.color,
true => theme.colorScheme.error.darker,
},
),
),
),
Padding(
padding: const EdgeInsets.all(denseSpacing),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (genAiEnabled && appModel.consoleShowingError)
MiniIconButton(
icon: Image.asset(
'gemini_sparkle_192.png',
width: 16,
height: 16,
),
tooltip: 'Suggest fix',
onPressed:
() => suggestFix(
context: context,
appType: appModel.appType,
errorMessage: consoleOutput,
),
),
),
Padding(
padding: const EdgeInsets.all(denseSpacing),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (genAiEnabled && appModel.consoleShowingError)
MiniIconButton(
icon: const Icon(Icons.playlist_remove),
tooltip: 'Clear console',
onPressed: consoleOutput.isEmpty ? null : _clearConsole,
icon: Image.asset(
'gemini_sparkle_192.png',
width: 16,
height: 16,
),
tooltip: 'Suggest fix',
onPressed:
() => suggestFix(
context: context,
appType: appModel.appType,
errorMessage: widget.output.error,
),
),
],
),
MiniIconButton(
icon: const Icon(Icons.playlist_remove),
tooltip: 'Clear console',
onPressed:
widget.output.isEmpty
? null
: () => widget.output.clear(),
),
],
),
],
),
),
],
);
},
),
);
}

void _clearConsole() {
widget.output.value = '';
}

void _scrollToEnd() {
if (!mounted) return;
final controller = scrollController;
Expand Down
7 changes: 6 additions & 1 deletion pkgs/dartpad_ui/lib/execution/frame.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import '../model.dart';
class ExecutionServiceImpl implements ExecutionService {
final StreamController<String> _stdoutController =
StreamController<String>.broadcast();
final StreamController<String> _stderrController =
StreamController<String>.broadcast();

web.HTMLIFrameElement _frame;
late String _frameSrc;
Expand Down Expand Up @@ -53,6 +55,9 @@ class ExecutionServiceImpl implements ExecutionService {
@override
Stream<String> get onStdout => _stdoutController.stream;

@override
Stream<String> get onStderr => _stderrController.stream;

@override
set ignorePointer(bool ignorePointer) {
_frame.style.pointerEvents = ignorePointer ? 'none' : 'auto';
Expand Down Expand Up @@ -254,7 +259,7 @@ require(["dartpad_main", "dart_sdk"], function(dartpad_main, dart_sdk) {
// Ignore any exceptions before the iframe has completed
// initialization.
if (_readyCompleter.isCompleted) {
_stdoutController.add(data['message'] as String);
_stderrController.add(data['message'] as String);
}
} else if (type == 'ready' && !_readyCompleter.isCompleted) {
_readyCompleter.complete();
Expand Down
68 changes: 42 additions & 26 deletions pkgs/dartpad_ui/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,12 @@ class _DartPadMainPageState extends State<DartPadMainPage>
late final AppModel appModel;
late final AppServices appServices;
late final SplitViewController mainSplitter;
late final SplitViewController consoleSplitter;
late final TabController tabController;

final Key _executionWidgetKey = GlobalKey(debugLabel: 'execution-widget');
final GlobalKey _executionWidgetKey = GlobalKey(
debugLabel: 'execution-widget',
);
final ValueKey<String> _loadingOverlayKey = const ValueKey(
'loading-overlay-widget',
);
Expand All @@ -259,6 +262,13 @@ class _DartPadMainPageState extends State<DartPadMainPage>
appModel.splitDragStateManager.handleSplitChanged();
});

consoleSplitter = SplitViewController(
weights: [0.7, 0.3],
limits: [WeightLimit(max: 0.9), WeightLimit(min: 0.1)],
)..addListener(() {
appModel.splitDragStateManager.handleSplitChanged();
});

final channel =
widget.initialChannel != null
? Channel.forName(widget.initialChannel!)
Expand Down Expand Up @@ -338,28 +348,34 @@ class _DartPadMainPageState extends State<DartPadMainPage>
ValueListenableBuilder(
valueListenable: appModel.layoutMode,
builder: (context, LayoutMode mode, _) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final domHeight = mode.calcDomHeight(constraints.maxHeight);
final consoleHeight = mode.calcConsoleHeight(
constraints.maxHeight,
);

return Column(
children: [
SizedBox(height: domHeight, child: executionWidget),
SizedBox(
height: consoleHeight,
child: ConsoleWidget(
output: appModel.consoleOutput,
showDivider: mode == LayoutMode.both,
key: _consoleKey,
),
return switch (mode) {
LayoutMode.both => SplitView(
viewMode: SplitViewMode.Vertical,
gripColor: theme.colorScheme.surface,
gripColorActive: theme.colorScheme.surface,
gripSize: defaultGripSize,
controller: consoleSplitter,
children: [
executionWidget,
ConsoleWidget(
key: _consoleKey,
output: appModel.consoleNotifier,
),
],
),
LayoutMode.justDom => executionWidget,
LayoutMode.justConsole => Column(
children: [
SizedBox(height: 0, width: 0, child: executionWidget),
Expanded(
child: ConsoleWidget(
key: _consoleKey,
output: appModel.consoleNotifier,
),
],
);
},
);
),
],
),
};
},
),
loadingOverlay,
Expand Down Expand Up @@ -495,7 +511,7 @@ class _DartPadMainPageState extends State<DartPadMainPage>
appServices.editorService!.focus();
} catch (error) {
appModel.editorStatus.showToast('Error formatting code');
appModel.appendLineToConsole('Formatting issue: $error');
appModel.appendError('Formatting issue: $error');
return;
}
}
Expand Down Expand Up @@ -709,7 +725,7 @@ class DartPadAppBar extends StatelessWidget implements PreferredSizeWidget {
appServices.performCompileAndReloadOrRun();
} catch (error) {
appModel.editorStatus.showToast('Error generating code');
appModel.appendLineToConsole('Generating code issue: $error');
appModel.appendError('Generating code issue: $error');
}
}

Expand Down Expand Up @@ -782,7 +798,7 @@ class DartPadAppBar extends StatelessWidget implements PreferredSizeWidget {
appServices.performCompileAndReloadOrRun();
} catch (error) {
appModel.editorStatus.showToast('Error updating code');
appModel.appendLineToConsole('Updating code issue: $error');
appModel.appendError('Updating code issue: $error');
}
}
}
Expand Down Expand Up @@ -957,7 +973,7 @@ class EditorWithButtons extends StatelessWidget {
appServices.editorService!.focus();
} catch (error) {
appModel.editorStatus.showToast('Error retrieving docs');
appModel.appendLineToConsole('$error');
appModel.appendError('$error');
return;
}
}
Expand Down
Loading

0 comments on commit 3219159

Please sign in to comment.