Integrate DTD with the Simulated DevTools Environment (#7133)
diff --git a/packages/.vscode/launch.json b/packages/.vscode/launch.json index b6f51bc..3e537d8 100644 --- a/packages/.vscode/launch.json +++ b/packages/.vscode/launch.json
@@ -119,5 +119,14 @@ "type": "dart", "request": "attach", }, + { + "name": "foo_devtools_extension example + simulated environment", + "request": "launch", + "type": "dart", + "program": "devtools_extensions/example/foo/packages/foo_devtools_extension/lib/main.dart", + "args": [ + "--dart-define=use_simulated_environment=true" + ], + }, ] }
diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md index 98787fb..95e563c 100644 --- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md +++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
@@ -66,6 +66,10 @@ ## DevTools Extension updates +* Added support for connecting to the Dart Tooling Daemon from the +simulated DevTools environment. - [#7133](https://github.com/flutter/devtools/pull/7133) +* Added help buttons to the VM Service and DTD connection text fields in the +simulated DevTools environment. - [#7133](https://github.com/flutter/devtools/pull/7133) * Fixed an issue with not detecting extensions for test files in subdirectories. - [#7174](https://github.com/flutter/devtools/pull/7174) * Added an example of creating an extension for a pure Dart package. - [#7196](https://github.com/flutter/devtools/pull/7196)
diff --git a/packages/devtools_extensions/CHANGELOG.md b/packages/devtools_extensions/CHANGELOG.md index b5be606..7cab68a 100644 --- a/packages/devtools_extensions/CHANGELOG.md +++ b/packages/devtools_extensions/CHANGELOG.md
@@ -1,5 +1,9 @@ ## 0.0.14-wip * Add a global `dtdManager` for interacting with the Dart Tooling Daemon. +* Add support for connecting to the Dart Tooling Daemon from the +simulated DevTools environment. +* Add help buttons to the VM Service and DTD connection text fields in the +simulated DevTools environment. * Bump `package:vm_service` dependency to ^14.0.0. * Refactor `example` directory to support more package examples. * Add an example of providing an extension from a pure Dart package.
diff --git a/packages/devtools_extensions/integration_test/test/simulated_environment_test.dart b/packages/devtools_extensions/integration_test/test/simulated_environment_test.dart index d5272ee..0fe030d 100644 --- a/packages/devtools_extensions/integration_test/test/simulated_environment_test.dart +++ b/packages/devtools_extensions/integration_test/test/simulated_environment_test.dart
@@ -214,7 +214,7 @@ // Check that the [environmentPanelSizedBoxWidth] is the expected width. expect( environmentPanelSizedBoxWidth, - VmServiceConnection.totalControlsWidth + 2 * defaultSpacing, + VmServiceConnectionDisplay.totalControlsWidth + 2 * defaultSpacing, ); // Drag the divider to the right by [environmentPanelSizedBoxWidth].
diff --git a/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/_connect_ui.dart b/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/_connect_ui.dart deleted file mode 100644 index 69d0ff8..0000000 --- a/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/_connect_ui.dart +++ /dev/null
@@ -1,142 +0,0 @@ -// Copyright 2023 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -part of '_simulated_devtools_environment.dart'; - -@visibleForTesting -class VmServiceConnection extends StatelessWidget { - const VmServiceConnection({ - super.key, - required this.simController, - required this.connected, - }); - - @visibleForTesting - static const totalControlsHeight = 45.0; - @visibleForTesting - static const totalControlsWidth = 415.0; - - final SimulatedDevToolsController simController; - final bool connected; - - @override - Widget build(BuildContext context) { - return SizedBox( - height: totalControlsHeight, - child: connected - ? _ConnectedVmServiceDisplay(simController: simController) - : _DisconnectedVmServiceDisplay(simController: simController), - ); - } -} - -class _ConnectedVmServiceDisplay extends StatelessWidget { - const _ConnectedVmServiceDisplay({required this.simController}); - - final SimulatedDevToolsController simController; - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Debugging:', - style: theme.regularTextStyle, - ), - Text( - serviceManager.service!.wsUri ?? '--', - style: theme.boldTextStyle, - ), - ], - ), - const Expanded( - child: SizedBox(width: denseSpacing), - ), - DevToolsButton( - elevated: true, - label: 'Disconnect', - onPressed: () { - simController.updateVmServiceConnection(uri: null); - }, - ), - ], - ); - } -} - -class _DisconnectedVmServiceDisplay extends StatefulWidget { - const _DisconnectedVmServiceDisplay({required this.simController}); - - final SimulatedDevToolsController simController; - - @override - State<_DisconnectedVmServiceDisplay> createState() => - _DisconnectedVmServiceDisplayState(); -} - -class _DisconnectedVmServiceDisplayState - extends State<_DisconnectedVmServiceDisplay> { - static const _connectFieldWidth = 300.0; - - late final TextEditingController _connectTextFieldController; - - @override - void initState() { - super.initState(); - _connectTextFieldController = TextEditingController(); - } - - @override - void dispose() { - _connectTextFieldController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox( - width: _connectFieldWidth, - child: TextField( - autofocus: true, - style: theme.regularTextStyle, - decoration: InputDecoration( - contentPadding: const EdgeInsets.all(denseSpacing), - isDense: true, - border: const OutlineInputBorder(), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide(width: 0.5, color: theme.focusColor), - ), - labelText: 'Dart VM Service URL', - labelStyle: theme.regularTextStyle, - hintText: '(e.g., http://127.0.0.1:60851/fH-kAEXc7MQ=/)', - hintStyle: theme.regularTextStyle, - ), - onSubmitted: (value) => - widget.simController.updateVmServiceConnection(uri: value), - controller: _connectTextFieldController, - ), - ), - const SizedBox(width: denseSpacing), - DevToolsButton( - elevated: true, - label: 'Connect', - onPressed: () { - widget.simController.updateVmServiceConnection( - uri: _connectTextFieldController.text, - ); - }, - ), - ], - ); - } -}
diff --git a/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/_simulated_devtools_controller.dart b/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/_simulated_devtools_controller.dart index df34f80..1068e44 100644 --- a/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/_simulated_devtools_controller.dart +++ b/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/_simulated_devtools_controller.dart
@@ -117,22 +117,12 @@ Future<void> hotReloadConnectedApp() async { await serviceManager.performHotReload(); - messageLogs.add( - MessageLogEntry( - source: MessageSource.info, - message: 'Hot reload performed on connected app', - ), - ); + logInfoEvent('Hot reload performed on connected app'); } Future<void> hotRestartConnectedApp() async { await serviceManager.performHotRestart(); - messageLogs.add( - MessageLogEntry( - source: MessageSource.info, - message: 'Hot restart performed on connected app', - ), - ); + logInfoEvent('Hot restart performed on connected app'); } void toggleTheme() { @@ -144,6 +134,12 @@ ); } + void logInfoEvent(String message) { + messageLogs.add( + MessageLogEntry(source: MessageSource.info, message: message), + ); + } + @override void forceReload() { _postMessageToExtension(
diff --git a/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/_simulated_devtools_environment.dart b/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/_simulated_devtools_environment.dart index 2d9faa9..86d6d61 100644 --- a/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/_simulated_devtools_environment.dart +++ b/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/_simulated_devtools_environment.dart
@@ -10,15 +10,18 @@ import 'package:devtools_app_shared/ui.dart'; import 'package:devtools_app_shared/utils.dart'; import 'package:flutter/material.dart'; -import 'package:web/web.dart' hide Text; +import 'package:flutter/services.dart'; +import 'package:web/web.dart' hide Text, Clipboard; import '../../api/api.dart'; import '../../api/model.dart'; import '../../utils.dart'; import '../devtools_extension.dart'; -part '_connect_ui.dart'; part '_simulated_devtools_controller.dart'; +part 'connection_ui/_connect.dart'; +part 'connection_ui/_dtd_connect.dart'; +part 'connection_ui/_vm_service_connect.dart'; /// Wraps [child] in a simulated DevTools environment. /// @@ -33,12 +36,15 @@ super.key, required this.child, required this.requiresRunningApplication, + required this.onDtdConnectionChange, }); final Widget child; final bool requiresRunningApplication; + final Future<void> Function(String?) onDtdConnectionChange; + @override State<SimulatedDevToolsWrapper> createState() => SimulatedDevToolsWrapperState(); @@ -51,9 +57,10 @@ late final ScrollController scrollController; - late ConnectedState connectionState; + bool get vmServiceConnected => vmServiceConnectionState.connected; + late ConnectedState vmServiceConnectionState; - bool get connected => connectionState.connected; + bool dtdConnected = false; @override void initState() { @@ -62,10 +69,17 @@ scrollController = ScrollController(); - connectionState = serviceManager.connectedState.value; + vmServiceConnectionState = serviceManager.connectedState.value; addAutoDisposeListener(serviceManager.connectedState, () { setState(() { - connectionState = serviceManager.connectedState.value; + vmServiceConnectionState = serviceManager.connectedState.value; + }); + }); + + dtdConnected = dtdManager.hasConnection; + addAutoDisposeListener(dtdManager.connection, () { + setState(() { + dtdConnected = dtdManager.hasConnection; }); }); } @@ -84,7 +98,7 @@ builder: (context, constraints) { final availableWidth = constraints.maxWidth; const environmentPanelMinWidth = - VmServiceConnection.totalControlsWidth + 2 * defaultSpacing; + VmServiceConnectionDisplay.totalControlsWidth + 2 * defaultSpacing; final environmentPanelFraction = environmentPanelMinWidth / availableWidth; @@ -129,49 +143,25 @@ style: theme.textTheme.titleMedium, ), const PaddedDivider(), - VmServiceConnection( - connected: connected, + VmServiceConnectionDisplay( + connected: vmServiceConnected, simController: simController, ), - const SizedBox(height: denseSpacing), - // TODO(kenz): figure out how to simulate the DTD - // connection. This may be non-trivial. + const PaddedDivider(), + DTDConnectionDisplay( + simController: simController, + connected: dtdConnected, + onConnectionChange: widget.onDtdConnectionChange, + ), _SimulatedApi( simController: simController, requiresRunningApplication: widget.requiresRunningApplication, - connectedToApplication: connected, + connectedToApplication: vmServiceConnected, ), const PaddedDivider(), Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Logs:', - style: theme.textTheme.titleMedium, - ), - DevToolsButton.iconOnly( - icon: Icons.clear, - outlined: false, - tooltip: 'Clear logs', - onPressed: () => - simController.messageLogs.clear(), - ), - ], - ), - const PaddedDivider.thin(), - Expanded( - child: _LogMessages( - simController: simController, - ), - ), - ], - ), + child: _LogsView(simController: simController), ), ], ), @@ -254,6 +244,42 @@ } } +class _LogsView extends StatelessWidget { + const _LogsView({required this.simController}); + + final SimulatedDevToolsController simController; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Logs:', + style: Theme.of(context).textTheme.titleMedium, + ), + DevToolsButton.iconOnly( + icon: Icons.clear, + outlined: false, + tooltip: 'Clear logs', + onPressed: () => simController.messageLogs.clear(), + ), + ], + ), + const PaddedDivider.thin(), + Expanded( + child: _LogMessages( + simController: simController, + ), + ), + ], + ); + } +} + class _LogMessages extends StatefulWidget { const _LogMessages({required this.simController});
diff --git a/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/connection_ui/_connect.dart b/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/connection_ui/_connect.dart new file mode 100644 index 0000000..5ab7cf4 --- /dev/null +++ b/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/connection_ui/_connect.dart
@@ -0,0 +1,220 @@ +// Copyright 2023 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +part of '../_simulated_devtools_environment.dart'; + +class _UriConnectionDisplay extends StatelessWidget { + const _UriConnectionDisplay({ + required this.simController, + required this.connected, + required this.connectedLabel, + required this.disconnectedLabel, + required this.disconnectedHint, + required this.onConnect, + required this.onDisconnect, + required this.currentConnection, + this.help, + }); + + @visibleForTesting + static const totalControlsHeight = 45.0; + + final SimulatedDevToolsController simController; + final bool connected; + final String connectedLabel; + final String disconnectedLabel; + final String disconnectedHint; + final void Function(String) onConnect; + final VoidCallback onDisconnect; + final String Function() currentConnection; + final Widget? help; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: totalControlsHeight, + child: connected + ? _ConnectedDisplay( + simController: simController, + label: connectedLabel, + onDisconnect: onDisconnect, + currentConnection: currentConnection, + help: help, + ) + : _DisconnectedDisplay( + simController: simController, + label: disconnectedLabel, + hint: disconnectedHint, + onConnect: onConnect, + help: help, + ), + ); + } +} + +class _ConnectedDisplay extends StatelessWidget { + const _ConnectedDisplay({ + required this.simController, + required this.label, + required this.currentConnection, + required this.onDisconnect, + this.help, + }); + + final SimulatedDevToolsController simController; + final String label; + final String Function() currentConnection; + final VoidCallback onDisconnect; + final Widget? help; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: theme.regularTextStyle, + ), + Text( + currentConnection(), + style: theme.boldTextStyle, + ), + ], + ), + const Expanded( + child: SizedBox(width: denseSpacing), + ), + DevToolsButton( + elevated: true, + label: 'Disconnect', + onPressed: onDisconnect, + ), + if (help != null) ...[ + const SizedBox(width: denseSpacing), + _ConnectionHelpButton( + dialogTitle: '$label help', + child: help!, + ), + ], + ], + ); + } +} + +class _DisconnectedDisplay extends StatefulWidget { + const _DisconnectedDisplay({ + required this.simController, + required this.label, + required this.hint, + required this.onConnect, + this.help, + }); + + final SimulatedDevToolsController simController; + final String label; + final String hint; + final void Function(String) onConnect; + final Widget? help; + + @override + State<_DisconnectedDisplay> createState() => _DisconnectedDisplayState(); +} + +class _DisconnectedDisplayState extends State<_DisconnectedDisplay> { + static const _connectFieldWidth = 300.0; + + late final TextEditingController _connectTextFieldController; + + @override + void initState() { + super.initState(); + _connectTextFieldController = TextEditingController(); + } + + @override + void dispose() { + _connectTextFieldController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: _connectFieldWidth, + child: TextField( + autofocus: true, + style: theme.regularTextStyle, + decoration: InputDecoration( + contentPadding: const EdgeInsets.all(denseSpacing), + isDense: true, + border: const OutlineInputBorder(), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(width: 0.5, color: theme.focusColor), + ), + labelText: widget.label, + labelStyle: theme.regularTextStyle, + hintText: widget.hint, + hintStyle: theme.regularTextStyle, + ), + onSubmitted: widget.onConnect, + controller: _connectTextFieldController, + ), + ), + const SizedBox(width: denseSpacing), + DevToolsButton( + elevated: true, + label: 'Connect', + onPressed: () => widget.onConnect(_connectTextFieldController.text), + ), + if (widget.help != null) ...[ + const SizedBox(width: denseSpacing), + _ConnectionHelpButton( + dialogTitle: '${widget.label} help', + child: widget.help!, + ), + ], + ], + ); + } +} + +class _ConnectionHelpButton extends StatelessWidget { + const _ConnectionHelpButton({ + required this.dialogTitle, + required this.child, + }); + + final String dialogTitle; + + final Widget child; + + static const helpContentWidth = 550.0; + + @override + Widget build(BuildContext context) { + return DevToolsButton.iconOnly( + icon: Icons.help_outline, + tooltip: 'Help', + onPressed: () { + showDevToolsDialog( + context: context, + title: dialogTitle, + content: SizedBox( + width: helpContentWidth, + child: child, + ), + ); + }, + ); + } +}
diff --git a/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/connection_ui/_dtd_connect.dart b/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/connection_ui/_dtd_connect.dart new file mode 100644 index 0000000..9316bc6 --- /dev/null +++ b/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/connection_ui/_dtd_connect.dart
@@ -0,0 +1,167 @@ +// Copyright 2024 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +part of '../_simulated_devtools_environment.dart'; + +@visibleForTesting +class DTDConnectionDisplay extends StatelessWidget { + const DTDConnectionDisplay({ + super.key, + required this.simController, + required this.connected, + required this.onConnectionChange, + }); + + @visibleForTesting + static const totalControlsHeight = 45.0; + @visibleForTesting + static const totalControlsWidth = 415.0; + + final SimulatedDevToolsController simController; + final bool connected; + final Future<void> Function(String?) onConnectionChange; + + @override + Widget build(BuildContext context) { + return _UriConnectionDisplay( + simController: simController, + connected: connected, + connectedLabel: 'Dart Tooling Daemon connection: ', + disconnectedLabel: 'Dart Tooling Daemon Connection', + disconnectedHint: '(e.g., ws://127.0.0.1:57624)', + // TODO(kenz): this needs handling for bad input. + onConnect: (value) async { + value = value.trim(); + if (value.startsWith('127.0.0.1')) { + value = 'ws://$value'; + } + await onConnectionChange(value); + if (dtdManager.hasConnection) { + simController.logInfoEvent( + 'Connected DTD to ${dtdManager.uri?.toString()}', + ); + } else { + simController.logInfoEvent('Failed to connect DTD to $value'); + } + }, + onDisconnect: () async { + await onConnectionChange(null); + simController.logInfoEvent('Disconnected from DTD'); + }, + currentConnection: () => dtdManager.uri?.toString() ?? '--', + help: const DtdHelp(), + ); + } +} + +class DtdHelp extends StatelessWidget { + const DtdHelp({super.key}); + + static const dtdStartCommand = 'dart tooling-daemon --unrestricted'; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return DefaultTextStyle( + style: theme.regularTextStyle, + child: Column( + children: [ + RichText( + text: TextSpan( + style: theme.regularTextStyle, + text: + 'If your DevTools extension interacts with the Dart Tooling ' + 'Daemon (DTD) through', + children: [ + TextSpan( + text: ' dtdManager ', + style: theme.boldTextStyle, + ), + const TextSpan( + text: 'then you will need to start a local instance of DTD ' + 'to debug these features in the simulated environment. ' + 'To start DTD locally:\n\n1. Run the following command ' + 'from your terminal:', + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: defaultSpacing), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const SelectableText(dtdStartCommand), + const SizedBox(width: defaultSpacing), + DevToolsButton.iconOnly( + icon: Icons.content_copy, + onPressed: () async { + await Clipboard.setData( + const ClipboardData(text: dtdStartCommand), + ); + }, + ), + ], + ), + ), + RichText( + text: TextSpan( + style: theme.regularTextStyle, + text: '2. This will output text to the command line:', + children: [ + TextSpan( + text: ' "The Dart Tooling Daemon is listening on ' + 'ws://127.0.0.1:62630/". ', + style: theme.boldTextStyle, + ), + const TextSpan( + text: 'Copy the DTD URI and paste it into the "Dart ' + 'Tooling Daemon Connection" text field to connect.', + ), + ], + ), + ), + const SizedBox(height: defaultSpacing), + Text( + 'In a real environment, DTD will be started by the user\'s IDE or' + ' by DevTools, so your extension will inherit the existing DTD' + ' connection from DevTools.', + style: theme.subtleTextStyle, + ), + const SizedBox(height: defaultSpacing), + RichText( + text: TextSpan( + text: 'WARNING: if you are using DTD\'s file system APIs (', + style: theme.regularTextStyle.copyWith( + color: theme.colorScheme.tertiary, + ), + children: [ + TextSpan( + text: 'readFileAsString, writeFileAsString, listDirectories', + style: theme.fixedFontStyle.copyWith( + color: theme.colorScheme.tertiary, + ), + ), + TextSpan( + text: + '), these will be more restricted in a real environment. ' + 'In the simulated environment, your extension can access ' + 'the entire file system; in a real environment, your ' + 'extension will only have access to files within the ' + 'user\'s project, which are determined by the workspace ' + 'directores in the IDE, or, if DevTools was launched from ' + 'the command line, then the project directories will be ' + 'determined by the project root of the connected app.', + style: theme.regularTextStyle.copyWith( + color: theme.colorScheme.tertiary, + ), + ), + ], + ), + ), + ], + ), + ); + } +}
diff --git a/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/connection_ui/_vm_service_connect.dart b/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/connection_ui/_vm_service_connect.dart new file mode 100644 index 0000000..c4720dc --- /dev/null +++ b/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/connection_ui/_vm_service_connect.dart
@@ -0,0 +1,83 @@ +// Copyright 2024 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +part of '../_simulated_devtools_environment.dart'; + +@visibleForTesting +class VmServiceConnectionDisplay extends StatelessWidget { + const VmServiceConnectionDisplay({ + super.key, + required this.simController, + required this.connected, + }); + + @visibleForTesting + static const totalControlsHeight = 45.0; + @visibleForTesting + static const totalControlsWidth = 415.0; + + final SimulatedDevToolsController simController; + final bool connected; + + @override + Widget build(BuildContext context) { + return _UriConnectionDisplay( + simController: simController, + connected: connected, + connectedLabel: 'Debugging: ', + disconnectedLabel: 'Dart VM Service Connection', + disconnectedHint: '(e.g., http://127.0.0.1:60851/fH-kAEXc7MQ=/)', + onConnect: (value) => simController.updateVmServiceConnection(uri: value), + onDisconnect: () => simController.updateVmServiceConnection(uri: null), + currentConnection: () => serviceManager.service!.wsUri ?? '--', + help: const VmServiceHelp(), + ); + } +} + +class VmServiceHelp extends StatelessWidget { + const VmServiceHelp({super.key}); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return DefaultTextStyle( + style: theme.regularTextStyle, + child: Column( + children: [ + Text( + 'If your DevTools extension interacts with a running Dart ' + 'application, then you will need to run a test application and ' + 'connect it to the simulated environment.\n\n' + '1. Run a Dart or Flutter application.\n', + style: theme.regularTextStyle, + ), + RichText( + text: TextSpan( + style: theme.regularTextStyle, + text: '2. This will output text to the command line:', + children: [ + TextSpan( + text: ' "A Dart VM Service is available at: ' + 'http://127.0.0.1:53985/6RVz1q0e9ok=". ', + style: theme.boldTextStyle, + ), + const TextSpan( + text: 'Copy the VM Service URI and paste it into the "Dart ' + 'VM Service Connection" text field to connect.', + ), + ], + ), + ), + const SizedBox(height: defaultSpacing), + Text( + 'In a real environment, your extension will inherit the existing ' + 'VM Service connection from DevTools.', + style: theme.subtleTextStyle, + ), + ], + ), + ); + } +}
diff --git a/packages/devtools_extensions/lib/src/template/devtools_extension.dart b/packages/devtools_extensions/lib/src/template/devtools_extension.dart index e74afe8..6ab6762 100644 --- a/packages/devtools_extensions/lib/src/template/devtools_extension.dart +++ b/packages/devtools_extensions/lib/src/template/devtools_extension.dart
@@ -197,6 +197,7 @@ body: _useSimulatedEnvironment ? SimulatedDevToolsWrapper( requiresRunningApplication: widget.requiresRunningApplication, + onDtdConnectionChange: extensionManager._connectToDtd, child: child, ) : child,