From 34b5a8d8f7fc687c217bdef2d6ed6561eacdf3c4 Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Wed, 26 Jul 2023 18:32:22 +0100 Subject: [PATCH 1/8] Add some simple side bar functionality with a mock editor environment --- packages/.vscode/launch.json | 15 ++ .../post_message/post_message.dart | 15 ++ .../post_message/post_message_stub.dart | 11 ++ .../post_message/post_message_web.dart | 19 ++ .../standalone_ui/api/dart_tooling_api.dart | 9 + .../api/impl/dart_tooling_api.dart | 116 +++++++++++++ .../standalone_ui/api/impl/vs_code_api.dart | 149 ++++++++++++++++ .../src/standalone_ui/api/vs_code_api.dart | 37 ++++ .../src/standalone_ui/standalone_screen.dart | 10 +- .../standalone_ui/vs_code/flutter_panel.dart | 109 +++++++++++- .../dart_tooling_mock_api.dart | 164 ++++++++++++++++++ .../flutter_panel_mock_editor.dart | 143 +++++++++++++++ packages/devtools_app/pubspec.yaml | 3 + 13 files changed, 794 insertions(+), 6 deletions(-) create mode 100644 packages/devtools_app/lib/src/shared/config_specific/post_message/post_message.dart create mode 100644 packages/devtools_app/lib/src/shared/config_specific/post_message/post_message_stub.dart create mode 100644 packages/devtools_app/lib/src/shared/config_specific/post_message/post_message_web.dart create mode 100644 packages/devtools_app/lib/src/standalone_ui/api/dart_tooling_api.dart create mode 100644 packages/devtools_app/lib/src/standalone_ui/api/impl/dart_tooling_api.dart create mode 100644 packages/devtools_app/lib/src/standalone_ui/api/impl/vs_code_api.dart create mode 100644 packages/devtools_app/lib/src/standalone_ui/api/vs_code_api.dart create mode 100644 packages/devtools_app/lib/src/standalone_ui/vs_code/mock_environment/dart_tooling_mock_api.dart create mode 100644 packages/devtools_app/lib/src/standalone_ui/vs_code/mock_environment/flutter_panel_mock_editor.dart diff --git a/packages/.vscode/launch.json b/packages/.vscode/launch.json index 9548c784464..6c3a9dbb39c 100644 --- a/packages/.vscode/launch.json +++ b/packages/.vscode/launch.json @@ -78,6 +78,21 @@ "type": "dart", "program": "devtools_app/test/test_infra/fixtures/memory_app/lib/main.dart", }, + { + "name": "VS Code Sidebar Environment", + "request": "launch", + "type": "dart", + "program": "devtools_app/lib/main.dart", + "args": [ + "--dart-define=enable_experiments=true", + // web-launch-url only works with an absolute URL so we must set hostname/port + // to match. However, this is convenient for using `dart.customDevToolsUri` for + // testing inside VS Code anyway. + "--web-hostname=localhost", + "--web-port=8765", + "--web-launch-url=http://localhost:8765/vsCodeFlutterPanelMockEditor", + ], + }, { "name": "attach", "type": "dart", diff --git a/packages/devtools_app/lib/src/shared/config_specific/post_message/post_message.dart b/packages/devtools_app/lib/src/shared/config_specific/post_message/post_message.dart new file mode 100644 index 00000000000..09463d76298 --- /dev/null +++ b/packages/devtools_app/lib/src/shared/config_specific/post_message/post_message.dart @@ -0,0 +1,15 @@ +// 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. + +export 'post_message_stub.dart' if (dart.library.html) 'post_message_web.dart'; + +class PostMessageEvent { + PostMessageEvent({ + required this.origin, + required this.data, + }); + + final String origin; + final Object? data; +} diff --git a/packages/devtools_app/lib/src/shared/config_specific/post_message/post_message_stub.dart b/packages/devtools_app/lib/src/shared/config_specific/post_message/post_message_stub.dart new file mode 100644 index 00000000000..063bdaa9d53 --- /dev/null +++ b/packages/devtools_app/lib/src/shared/config_specific/post_message/post_message_stub.dart @@ -0,0 +1,11 @@ +// 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. + +import 'post_message.dart'; + +Stream get onPostMessage => + throw UnsupportedError('unsupported platform'); + +void postMessage(Object? _, String __) => + throw UnsupportedError('unsupported platform'); diff --git a/packages/devtools_app/lib/src/shared/config_specific/post_message/post_message_web.dart b/packages/devtools_app/lib/src/shared/config_specific/post_message/post_message_web.dart new file mode 100644 index 00000000000..7630660cd01 --- /dev/null +++ b/packages/devtools_app/lib/src/shared/config_specific/post_message/post_message_web.dart @@ -0,0 +1,19 @@ +// 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. + +import 'dart:html' as html; + +import 'post_message.dart'; + +Stream get onPostMessage { + return html.window.onMessage.map( + (message) => PostMessageEvent( + origin: message.origin, + data: message.data, + ), + ); +} + +void postMessage(Object? message, String targetOrigin) => + html.window.parent?.postMessage(message, targetOrigin); diff --git a/packages/devtools_app/lib/src/standalone_ui/api/dart_tooling_api.dart b/packages/devtools_app/lib/src/standalone_ui/api/dart_tooling_api.dart new file mode 100644 index 00000000000..cbaf7ae894d --- /dev/null +++ b/packages/devtools_app/lib/src/standalone_ui/api/dart_tooling_api.dart @@ -0,0 +1,9 @@ +// 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. + +import 'vs_code_api.dart'; + +abstract interface class DartToolingApi { + Future get vsCode; +} diff --git a/packages/devtools_app/lib/src/standalone_ui/api/impl/dart_tooling_api.dart b/packages/devtools_app/lib/src/standalone_ui/api/impl/dart_tooling_api.dart new file mode 100644 index 00000000000..2df5d57a8cc --- /dev/null +++ b/packages/devtools_app/lib/src/standalone_ui/api/impl/dart_tooling_api.dart @@ -0,0 +1,116 @@ +// 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. + +import 'dart:async'; +import 'dart:convert'; + +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc_2; +import 'package:logging/logging.dart'; +import 'package:meta/meta.dart'; +import 'package:stream_channel/stream_channel.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; + +import '../../../shared/config_specific/logger/logger_helpers.dart'; +import '../../../shared/config_specific/post_message/post_message.dart'; +import '../../../shared/constants.dart'; +import '../dart_tooling_api.dart'; +import '../vs_code_api.dart'; +import 'vs_code_api.dart'; + +final _log = Logger('tooling_api'); + +/// Whether to enable verbose logging for postMessage communication. +/// +/// This is useful for debugging when running inside VS Code. +/// +/// TODO(dantup): Make a way for this to be enabled by users at runtime for +/// troubleshooting. This could be via a message from VS Code, or something +/// that passes a query param. +const _enablePostMessageVerboseLogging = false; + +/// An API for interacting with Dart tooling. +class DartToolingApiImpl implements DartToolingApi { + DartToolingApiImpl.rpc(this._rpc) { + unawaited(_rpc.listen()); + } + + /// Connects the API using 'postMessage'. This is only available when running + /// on web and embedded inside VS Code. + factory DartToolingApiImpl.postMessage() { + if (_enablePostMessageVerboseLogging) { + setDevToolsLoggingLevel(verboseLoggingLevel); + } + final postMessageController = StreamController(); + postMessageController.stream.listen((message) { + _log.info('==> $message'); + postMessage(message, '*'); + }); + final channel = StreamChannel( + onPostMessage.map((event) { + _log.info('<== ${jsonEncode(event.data)}'); + return event.data; + }), + postMessageController, + ); + return DartToolingApiImpl.rpc(json_rpc_2.Peer.withoutJson(channel)); + } + + /// Connects the API over the provided WebSocket. + factory DartToolingApiImpl.webSocket(WebSocketChannel socket) { + return DartToolingApiImpl.rpc(json_rpc_2.Peer(socket.cast())); + } + + final json_rpc_2.Peer _rpc; + + /// An API that provides Access to APIs related to VS Code, such as executing + /// VS Code commands or interacting with the Dart/Flutter extensions. + /// + /// Lazy-initialized and completes with `null` if VS Code is not available. + @override + late final Future vsCode = VsCodeApiImpl.tryConnect(_rpc); + + void dispose() { + unawaited(_rpc.close()); + } +} + +/// Base class for the different APIs that may be available. +abstract base class ToolApiImpl { + ToolApiImpl(this.rpc); + + static Future?> tryGetCapabilities( + json_rpc_2.Peer rpc, + String apiName, + ) async { + try { + final response = await rpc.sendRequest('$apiName.getCapabilities') + as Map; + return response.cast(); + } catch (_) { + // Any error initializing should disable this functionality. + return null; + } + } + + @protected + final json_rpc_2.Peer rpc; + + @protected + String get apiName; + + @protected + Future sendRequest(String method, [Object? parameters]) async { + return (await rpc.sendRequest('$apiName.$method', parameters)) as T; + } + + /// Listens for an event '[apiName].[name]' that has a Map for parameters. + @protected + Stream> events(String name) { + final streamController = StreamController>.broadcast(); + rpc.registerMethod('$apiName.$name', (json_rpc_2.Parameters parameters) { + streamController.add(parameters.asMap.cast()); + }); + return streamController.stream; + } +} diff --git a/packages/devtools_app/lib/src/standalone_ui/api/impl/vs_code_api.dart b/packages/devtools_app/lib/src/standalone_ui/api/impl/vs_code_api.dart new file mode 100644 index 00000000000..229415f25b9 --- /dev/null +++ b/packages/devtools_app/lib/src/standalone_ui/api/impl/vs_code_api.dart @@ -0,0 +1,149 @@ +// 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. + +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc_2; +import 'package:meta/meta.dart'; + +import '../vs_code_api.dart'; +import 'dart_tooling_api.dart'; + +final class VsCodeApiImpl extends ToolApiImpl implements VsCodeApi { + VsCodeApiImpl._(super.rpc, Map capabilities) { + this.capabilities = VsCodeCapabilitiesImpl(capabilities); + devicesChanged = + events('devicesChanged').map(VsCodeDevicesEventImpl.fromJson); + } + + static Future tryConnect(json_rpc_2.Peer rpc) async { + final capabilities = await ToolApiImpl.tryGetCapabilities(rpc, _apiName); + return capabilities != null ? VsCodeApiImpl._(rpc, capabilities) : null; + } + + static const _apiName = 'vsCode'; + + @override + Future initialize() => sendRequest('initialize'); + + @override + @protected + String get apiName => _apiName; + + @override + late final Stream devicesChanged; + + @override + late final VsCodeCapabilities capabilities; + + @override + Future executeCommand(String command, [List? arguments]) { + return sendRequest( + 'executeCommand', + {'command': command, 'arguments': arguments}, + ); + } + + @override + Future selectDevice(String id) { + return sendRequest( + 'selectDevice', + {'id': id}, + ); + } +} + +class VsCodeDeviceImpl implements VsCodeDevice { + VsCodeDeviceImpl({ + required this.id, + required this.name, + required this.category, + required this.emulator, + required this.emulatorId, + required this.ephemeral, + required this.platform, + required this.platformType, + }); + + VsCodeDeviceImpl.fromJson(Map json) + : this( + id: json['id'] as String, + name: json['name'] as String, + category: json['category'] as String?, + emulator: json['emulator'] as bool, + emulatorId: json['emulatorId'] as String?, + ephemeral: json['ephemeral'] as bool, + platform: json['platform'] as String, + platformType: json['platformType'] as String?, + ); + + @override + final String id; + + @override + final String name; + + @override + final String? category; + + @override + final bool emulator; + + @override + final String? emulatorId; + + @override + final bool ephemeral; + + @override + final String platform; + + @override + final String? platformType; + + Map toJson() => { + 'id': id, + 'name': name, + 'category': category, + 'emulator': emulator, + 'emulatorId': emulatorId, + 'ephemeral': ephemeral, + 'platform': platform, + 'platformType': platformType, + }; +} + +class VsCodeDevicesEventImpl implements VsCodeDevicesEvent { + VsCodeDevicesEventImpl({ + required this.selectedDeviceId, + required this.devices, + }); + + VsCodeDevicesEventImpl.fromJson(Map json) + : this( + selectedDeviceId: json['selectedDeviceId'] as String?, + devices: (json['devices'] as List) + .map((item) => Map.from(item)) + .map((map) => VsCodeDeviceImpl.fromJson(map)) + .toList(), + ); + + @override + final String? selectedDeviceId; + + @override + final List devices; + + Map toJson() => { + 'selectedDeviceId': selectedDeviceId, + 'devices': devices, + }; +} + +class VsCodeCapabilitiesImpl implements VsCodeCapabilities { + VsCodeCapabilitiesImpl(this._raw); + + final Map? _raw; + + @override + bool get executeCommand => _raw?['executeCommand'] == true; +} diff --git a/packages/devtools_app/lib/src/standalone_ui/api/vs_code_api.dart b/packages/devtools_app/lib/src/standalone_ui/api/vs_code_api.dart new file mode 100644 index 00000000000..4872d82ec1e --- /dev/null +++ b/packages/devtools_app/lib/src/standalone_ui/api/vs_code_api.dart @@ -0,0 +1,37 @@ +// 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. + +/// This class defines the API exposed by the Dart/Flutter extensions in VS +/// Code (and must match the implementation there). +/// +/// All changes to this file should be backwards-compatible and use +/// [VsCodeCapabilities] to advertise which capabilities are available and +/// handle any changes in behaviour. +abstract interface class VsCodeApi { + VsCodeCapabilities get capabilities; + Future initialize(); + Stream get devicesChanged; + Future executeCommand(String command, [List? arguments]); + Future selectDevice(String id); +} + +abstract interface class VsCodeDevice { + String get id; + String get name; + String? get category; + bool get emulator; + String? get emulatorId; + bool get ephemeral; + String get platform; + String? get platformType; +} + +abstract interface class VsCodeDevicesEvent { + String? get selectedDeviceId; + List get devices; +} + +abstract interface class VsCodeCapabilities { + bool get executeCommand; +} diff --git a/packages/devtools_app/lib/src/standalone_ui/standalone_screen.dart b/packages/devtools_app/lib/src/standalone_ui/standalone_screen.dart index 557be38917d..9dec5f00233 100644 --- a/packages/devtools_app/lib/src/standalone_ui/standalone_screen.dart +++ b/packages/devtools_app/lib/src/standalone_ui/standalone_screen.dart @@ -4,7 +4,9 @@ import 'package:flutter/material.dart'; +import 'api/impl/dart_tooling_api.dart'; import 'vs_code/flutter_panel.dart'; +import 'vs_code/mock_environment/flutter_panel_mock_editor.dart'; /// "Screens" that are intended for standalone use only, likely for embedding /// directly in an IDE. @@ -13,7 +15,8 @@ import 'vs_code/flutter_panel.dart'; /// meaning that this screen will not be part of DevTools' normal navigation. /// The only way to access a standalone screen is directly from the url. enum StandaloneScreenType { - vsCodeFlutterPanel; + vsCodeFlutterPanel, + vsCodeFlutterPanelMockEditor; static StandaloneScreenType? parse(String? id) { if (id == null) return null; @@ -26,7 +29,10 @@ enum StandaloneScreenType { Widget get screen { return switch (this) { - StandaloneScreenType.vsCodeFlutterPanel => const VsCodeFlutterPanel(), + StandaloneScreenType.vsCodeFlutterPanel => + VsCodeFlutterPanel(DartToolingApiImpl.postMessage()), + StandaloneScreenType.vsCodeFlutterPanelMockEditor => + const VsCodeFlutterPanelMockEditor(), }; } } diff --git a/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart b/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart index 72b3053aa16..60b5691411e 100644 --- a/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart +++ b/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart @@ -2,18 +2,119 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; + import 'package:flutter/material.dart'; +import '../../../devtools_app.dart'; import '../../shared/feature_flags.dart'; +import '../api/dart_tooling_api.dart'; +import '../api/vs_code_api.dart'; + +/// A general Flutter sidebar panel for embedding inside IDEs. +/// +/// Provides some basic functionality to improve discoverability of features +/// such as creation of new projects, device selection and DevTools features. +class VsCodeFlutterPanel extends StatefulWidget { + const VsCodeFlutterPanel(this.api, {super.key}); + + final DartToolingApi api; -class VsCodeFlutterPanel extends StatelessWidget { - const VsCodeFlutterPanel({super.key}); + @override + State createState() => _VsCodeFlutterPanelState(); +} +class _VsCodeFlutterPanelState extends State { @override Widget build(BuildContext context) { assert(FeatureFlags.vsCodeSidebarTooling); - return const Center( - child: Text('TODO: a panel for flutter actions in VS Code'), + + final api = widget.api; + + return Column( + children: [ + const Text(''), + FutureBuilder( + future: api.vsCode, + builder: (context, snapshot) => + switch ((snapshot.connectionState, snapshot.data)) { + (ConnectionState.done, final vsCodeApi?) => + _VsCodeConnectedPanel(vsCodeApi), + (ConnectionState.done, null) => + const Text('VS Code is not available'), + _ => const CenteredCircularProgressIndicator(), + }, + ), + ], + ); + } +} + +/// The panel shown once we know VS Code is available (the host has responded to +/// the `vsCode.getCapabilities` request). +class _VsCodeConnectedPanel extends StatefulWidget { + const _VsCodeConnectedPanel(this.api, {super.key}); + + final VsCodeApi api; + + @override + State<_VsCodeConnectedPanel> createState() => _VsCodeConnectedPanelState(); +} + +class _VsCodeConnectedPanelState extends State<_VsCodeConnectedPanel> { + @override + void initState() { + super.initState(); + + unawaited(widget.api.initialize()); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + if (widget.api.capabilities.executeCommand) + ElevatedButton( + onPressed: () => + unawaited(widget.api.executeCommand('flutter.createProject')), + child: const Text('New Flutter Project'), + ), + StreamBuilder( + stream: widget.api.devicesChanged, + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const Text(''); + } + final deviceEvent = snapshot.data!; + return Table( + defaultVerticalAlignment: TableCellVerticalAlignment.middle, + children: [ + for (final device in deviceEvent.devices) + TableRow( + children: [ + TextButton( + child: Text(device.name), + onPressed: () => + unawaited(widget.api.selectDevice(device.id)), + ), + Text( + device.id == deviceEvent.selectedDeviceId + ? '(selected)' + : '', + ), + ], + ), + ], + ); + }, + ), + if (widget.api.capabilities.executeCommand) + ElevatedButton( + onPressed: () => + unawaited(widget.api.executeCommand('flutter.doctor')), + child: const Text('Run Flutter Doctor'), + ), + ], ); } } diff --git a/packages/devtools_app/lib/src/standalone_ui/vs_code/mock_environment/dart_tooling_mock_api.dart b/packages/devtools_app/lib/src/standalone_ui/vs_code/mock_environment/dart_tooling_mock_api.dart new file mode 100644 index 00000000000..3bd50ae4567 --- /dev/null +++ b/packages/devtools_app/lib/src/standalone_ui/vs_code/mock_environment/dart_tooling_mock_api.dart @@ -0,0 +1,164 @@ +// 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. + +import 'dart:async'; + +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc_2; +import 'package:stream_channel/stream_channel.dart'; + +import '../../api/impl/dart_tooling_api.dart'; +import '../../api/impl/vs_code_api.dart'; +import '../../api/vs_code_api.dart'; + +/// A [DartToolingApi] that acts as a stand-in host IDE to simplify the development +/// workflow when working on embedded tooling. +/// +/// This API will handle requests with canned responses and can generate +/// events in a similar way to the IDE would. It is used by +/// [VsCodeFlutterPanelMock] which provides a UI onto this functionality and a +/// log of recent requests. +class MockDartToolingApi extends DartToolingApiImpl { + factory MockDartToolingApi() { + // Set up channels where we can act as the server in-process without really + // going over postMessage or a WebSocket (since in the mock environment we + // can't do either). + final clientStreams = StreamController(); + final serverStreams = StreamController(); + + // Capture traffic in both directions to aid development/debugging. + final log = StreamController(); + var logLine = 1; + Stream logStream(Stream stream, String prefix) { + return stream.map((item) { + log.add('${logLine++} $prefix $item'); + return item; + }); + } + + final clientChannel = StreamChannel( + logStream(serverStreams.stream, '<=='), + clientStreams.sink, + ); + final serverChannel = StreamChannel( + logStream(clientStreams.stream, '==>'), + serverStreams.sink, + ); + + final clientPeer = json_rpc_2.Peer(clientChannel); + final serverPeer = json_rpc_2.Peer(serverChannel); + unawaited(serverPeer.listen()); + + return MockDartToolingApi._( + client: clientPeer, + server: serverPeer, + log: log.stream, + ); + } + + MockDartToolingApi._({ + required this.client, + required this.server, + required this.log, + }) : super.rpc(client) { + // Register methods as they'll be available in a real host. + server.registerMethod('vsCode.getCapabilities', () async { + return { + 'executeCommand': true, + }; + }); + server.registerMethod('vsCode.initialize', initialize); + server.registerMethod('vsCode.executeCommand', executeCommand); + server.registerMethod('vsCode.selectDevice', selectDevice); + } + + final json_rpc_2.Peer client; + final json_rpc_2.Peer server; + + /// A set of mock devices that can be presented for testing. + final _mockDevices = [ + VsCodeDeviceImpl( + id: 'myMac', + name: 'Mac', + category: 'desktop', + emulator: false, + emulatorId: null, + ephemeral: false, + platform: 'dartwin-x64', + platformType: 'macos', + ), + VsCodeDeviceImpl( + id: 'myPhone', + name: 'My Android Phone', + category: 'mobile', + emulator: false, + emulatorId: null, + ephemeral: true, + platform: 'android-x64', + platformType: 'android', + ), + ]; + + /// The current set of devices being presented to the embedded panel. + final List _devices = []; + + /// The currently selected device presented to the embedded panel. + String? _selectedDeviceId; + + /// A stream of log events for debugging. + final Stream log; + + /// Simulates executing a VS Code command requested by the embedded panel. + Future initialize() async { + connectDevices(); + } + + /// Simulates executing a VS Code command requested by the embedded panel. + Future executeCommand(json_rpc_2.Parameters parameters) async { + final params = parameters.asMap; + final command = params['command']; + switch (command) { + case 'flutter.createProject': + return null; + default: + throw 'Unknown command $command'; + } + } + + /// Simulates changing the selected device to [id] as requested by the + /// embedded panel. + Future selectDevice(json_rpc_2.Parameters parameters) async { + final params = parameters.asMap; + _selectedDeviceId = params['id'] as String?; + _sendDevicesChanged(); + return true; + } + + /// Simulates devices being connected in the IDE by notifying the embedded + /// panel about a set of test devices. + void connectDevices() { + _devices + ..clear() + ..addAll(_mockDevices); + _selectedDeviceId = _devices.lastOrNull?.id; + _sendDevicesChanged(); + } + + /// Simulates devices being disconnected in the IDE by notifying the embedded + /// panel about a now-empty set of devices. + void disconnectDevices() { + _devices.clear(); + _selectedDeviceId = null; + _sendDevicesChanged(); + } + + void _sendDevicesChanged() { + server.sendNotification( + 'vsCode.devicesChanged', + VsCodeDevicesEventImpl( + devices: _devices, + selectedDeviceId: _selectedDeviceId, + ).toJson(), + ); + } +} diff --git a/packages/devtools_app/lib/src/standalone_ui/vs_code/mock_environment/flutter_panel_mock_editor.dart b/packages/devtools_app/lib/src/standalone_ui/vs_code/mock_environment/flutter_panel_mock_editor.dart new file mode 100644 index 00000000000..277961edcec --- /dev/null +++ b/packages/devtools_app/lib/src/standalone_ui/vs_code/mock_environment/flutter_panel_mock_editor.dart @@ -0,0 +1,143 @@ +// 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. + +import 'dart:collection'; +import 'dart:convert'; + +import 'package:flutter/material.dart'; + +import '../../../shared/split.dart'; +import '../../../shared/theme.dart'; +import '../flutter_panel.dart'; +import 'dart_tooling_mock_api.dart'; + +/// A simple UI that acts as a stand-in host IDE to simplify the development +/// workflow when working on embedded tooling. +/// +/// This UI interacts with [MockDartToolingApi] to allow triggering events that +/// would normally be fired by the IDE and also shows a log of recent requests. +class VsCodeFlutterPanelMockEditor extends StatefulWidget { + const VsCodeFlutterPanelMockEditor({super.key}); + + @override + State createState() => + _VsCodeFlutterPanelMockEditorState(); +} + +class _VsCodeFlutterPanelMockEditorState + extends State { + /// The mock API to interact with. + final api = MockDartToolingApi(); + + /// The number of communication messages to keep in the log. + static const maxLogEvents = 20; + + /// The last [maxLogEvents] communication messages sent between the panel + /// and the "host IDE". + final logRing = DoubleLinkedQueue(); + + /// A stream that emits each time the log is updated to allow the log widget + /// to be rebuilt. + Stream? logUpdated; + + /// Flutter icon for the sidebar. + final sidebarImageBytes = base64Decode( + 'iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAABF1BMVEUAAAD///////+/v//MzMzb29vf39/j4+PV1erY2Njb29vS4eHX1+TZ2ebW1uDY2OLW3d3Y2N7Z2d/a2uDV2+DW2+DX3OHZ2eLZ2d7V2t/Y2OHX29/X29/Z2eDW2eDW2uDX2uHW2d/X2uDY2+HW2d/W2+HW2eHX2d/W2+DW2eDX2eHX2uHX29/X2d/Y2uDY2uDW2uDX2uDX2+DX2+DX2eDX2t/Y2+DX29/Y2eDW2eDX2uDX2uDW2d/X2uDX2uDY2uDX2uHX2eDX2uDX2uHY2t/X2+DX2uDY2uDX2uDX2uDX2+DW2uDX2eDX2uDX2uDX2uDX2eDX2uDX2uDX2uDX2uDX2uDX2uDX2uDX2uDX2uDX2uDX2uDX2uANs9umAAAAXHRSTlMAAgMEBQcICQwNDhETFBkaJScoKTEyMzU2Nzs/QElKS0xQU1VYXV5fY2RlbXh5e3yDi4yNjpmboaKjpKepqrO1ub7AwcLEzM/R2Nnc4OPk5efr7O3w8vT3+Pn7/A+G+WEAAAABYktHRAH/Ai3eAAAA0UlEQVQoz2NgQAKythCgwYAKFCLtTIHAO0YbVVw23AREqUTroYlH0FrcGK94FJq4HExcH5c4t5IyGAiCxeUjDUGUWrQOr0cMBJiDJYwiJYCkarQOt5sXP5Al4OvKBZZgsgqRBJsDERf0c+GE2sFsE2IAVy/k78wBt53ZJkYXKi4c4MCO5C4mCR53Tz4gQyTIng3VyVoxSiDK04cVLY6YLEOlQE4PN2NElzEPkwFS0qHWLNhlxIPt2LDLiAY6cmDaoygmJqYe4cSJLmMBDStNIAcAHhssjDYY1ccAAAAASUVORK5CYII=', + ); + + @override + void initState() { + super.initState(); + + // Listen to the log stream to maintain our buffer and trigger rebuilds. + logUpdated = api.log.map((log) { + logRing.add(log); + while (logRing.length > maxLogEvents) { + logRing.removeFirst(); + } + }); + } + + @override + Widget build(BuildContext context) { + return Split( + axis: Axis.horizontal, + initialFractions: const [0.2, 0.8], + minSizes: const [200, 200], + children: [ + Row( + children: [ + SizedBox( + width: 48, + child: Container( + alignment: Alignment.topCenter, + padding: const EdgeInsets.only(top: 60), + constraints: const BoxConstraints.expand(width: 48), + color: const Color(0xFF333333), + child: Image.memory(sidebarImageBytes), + ), + ), + Expanded(child: VsCodeFlutterPanel(api)), + ], + ), + Split( + axis: Axis.vertical, + initialFractions: const [0.5, 0.5], + minSizes: const [200, 200], + children: [ + Container( + color: const Color(0xFF282828), + padding: const EdgeInsets.all(10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Mock Editor', + style: Theme.of(context).textTheme.headlineMedium, + ), + const Text(''), + const Text( + 'Use these buttons to simulate actions that would usually occur in the IDE.', + ), + const Text(''), + Row( + children: [ + ElevatedButton( + onPressed: api.connectDevices, + child: const Text('Connect Devices'), + ), + ElevatedButton( + onPressed: api.disconnectDevices, + child: const Text('Disconnect Devices'), + ), + ], + ), + ], + ), + ), + Container( + color: const Color(0xFF222222), + padding: const EdgeInsets.all(10), + child: StreamBuilder( + stream: logUpdated, + builder: (context, snapshot) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (final log in logRing) + Text( + log, + style: Theme.of(context).fixedFontStyle, + ), + ], + ); + }, + ), + ), + ], + ), + ], + ); + } +} diff --git a/packages/devtools_app/pubspec.yaml b/packages/devtools_app/pubspec.yaml index 4ffb3c86840..eeeee9fab28 100644 --- a/packages/devtools_app/pubspec.yaml +++ b/packages/devtools_app/pubspec.yaml @@ -46,8 +46,10 @@ dependencies: image: ^3.0.2 intl: ">=0.16.1 <0.18.0" js: ^0.6.1+1 + json_rpc_2: ^3.0.2 leak_tracker: 2.0.1 logging: ^1.1.1 + meta: ^1.9.1 mime: ^1.0.0 path: ^1.8.0 perfetto_ui_compiled: @@ -58,6 +60,7 @@ dependencies: shared_preferences: ^2.0.15 sse: ^4.1.2 stack_trace: ^1.10.0 + stream_channel: ^2.1.1 string_scanner: ^1.1.0 url_launcher: ^6.1.0 url_launcher_web: ^2.0.6 From 32bb16d6fac0f5cb63a99b1e70921cdde2834abb Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Wed, 2 Aug 2023 16:21:56 +0100 Subject: [PATCH 2/8] Add additional comments --- .../lib/src/standalone_ui/api/dart_tooling_api.dart | 8 ++++++++ .../lib/src/standalone_ui/api/impl/dart_tooling_api.dart | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/devtools_app/lib/src/standalone_ui/api/dart_tooling_api.dart b/packages/devtools_app/lib/src/standalone_ui/api/dart_tooling_api.dart index cbaf7ae894d..d10ecb1f929 100644 --- a/packages/devtools_app/lib/src/standalone_ui/api/dart_tooling_api.dart +++ b/packages/devtools_app/lib/src/standalone_ui/api/dart_tooling_api.dart @@ -4,6 +4,14 @@ import 'vs_code_api.dart'; +/// An API exposed to Dart tooling extensions. +/// +/// APIs are grouped into child APIs that are exposed as fields. Each field is a +/// `Future` that will return null if the requested API is unavailable (for +/// example the VS Code APIs if not running inside VS Code, or the LSP APIs if +/// no LSP server is available). abstract interface class DartToolingApi { + /// Access to APIs provided by VS Code and/or the Dart/Flutter VS Code + /// extensions. Future get vsCode; } diff --git a/packages/devtools_app/lib/src/standalone_ui/api/impl/dart_tooling_api.dart b/packages/devtools_app/lib/src/standalone_ui/api/impl/dart_tooling_api.dart index 2df5d57a8cc..776d16657a5 100644 --- a/packages/devtools_app/lib/src/standalone_ui/api/impl/dart_tooling_api.dart +++ b/packages/devtools_app/lib/src/standalone_ui/api/impl/dart_tooling_api.dart @@ -18,8 +18,6 @@ import '../dart_tooling_api.dart'; import '../vs_code_api.dart'; import 'vs_code_api.dart'; -final _log = Logger('tooling_api'); - /// Whether to enable verbose logging for postMessage communication. /// /// This is useful for debugging when running inside VS Code. @@ -29,6 +27,8 @@ final _log = Logger('tooling_api'); /// that passes a query param. const _enablePostMessageVerboseLogging = false; +final _log = Logger('tooling_api'); + /// An API for interacting with Dart tooling. class DartToolingApiImpl implements DartToolingApi { DartToolingApiImpl.rpc(this._rpc) { @@ -36,7 +36,7 @@ class DartToolingApiImpl implements DartToolingApi { } /// Connects the API using 'postMessage'. This is only available when running - /// on web and embedded inside VS Code. + /// on web and hosted inside an iframe (such as inside a VS Code webview). factory DartToolingApiImpl.postMessage() { if (_enablePostMessageVerboseLogging) { setDevToolsLoggingLevel(verboseLoggingLevel); From c1db587f2ef9d3d89c58122abe3afe3714f2a7f9 Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Sun, 6 Aug 2023 12:24:47 +0100 Subject: [PATCH 3/8] Minor review comments - Add comments - Use more constants - StatelessWidget where state not required - Fix typos - Add `selectDevice` capability - ... --- .../standalone_ui/api/dart_tooling_api.dart | 2 +- .../api/impl/dart_tooling_api.dart | 10 +-- .../standalone_ui/api/impl/vs_code_api.dart | 72 ++++++++++--------- .../src/standalone_ui/api/vs_code_api.dart | 47 ++++++++++++ .../standalone_ui/vs_code/flutter_panel.dart | 70 +++++++++--------- .../dart_tooling_mock_api.dart | 5 +- 6 files changed, 126 insertions(+), 80 deletions(-) diff --git a/packages/devtools_app/lib/src/standalone_ui/api/dart_tooling_api.dart b/packages/devtools_app/lib/src/standalone_ui/api/dart_tooling_api.dart index d10ecb1f929..d5be958a4fc 100644 --- a/packages/devtools_app/lib/src/standalone_ui/api/dart_tooling_api.dart +++ b/packages/devtools_app/lib/src/standalone_ui/api/dart_tooling_api.dart @@ -4,7 +4,7 @@ import 'vs_code_api.dart'; -/// An API exposed to Dart tooling extensions. +/// An API exposed to Dart tooling surfaces. /// /// APIs are grouped into child APIs that are exposed as fields. Each field is a /// `Future` that will return null if the requested API is unavailable (for diff --git a/packages/devtools_app/lib/src/standalone_ui/api/impl/dart_tooling_api.dart b/packages/devtools_app/lib/src/standalone_ui/api/impl/dart_tooling_api.dart index 776d16657a5..f1e3490363c 100644 --- a/packages/devtools_app/lib/src/standalone_ui/api/impl/dart_tooling_api.dart +++ b/packages/devtools_app/lib/src/standalone_ui/api/impl/dart_tooling_api.dart @@ -9,7 +9,6 @@ import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc_2; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'package:stream_channel/stream_channel.dart'; -import 'package:web_socket_channel/web_socket_channel.dart'; import '../../../shared/config_specific/logger/logger_helpers.dart'; import '../../../shared/config_specific/post_message/post_message.dart'; @@ -29,7 +28,8 @@ const _enablePostMessageVerboseLogging = false; final _log = Logger('tooling_api'); -/// An API for interacting with Dart tooling. +/// An API used by Dart tooling surfaces to interact with Dart tools that expose +/// APIs such as Dart-Code and the LSP server. class DartToolingApiImpl implements DartToolingApi { DartToolingApiImpl.rpc(this._rpc) { unawaited(_rpc.listen()); @@ -56,11 +56,6 @@ class DartToolingApiImpl implements DartToolingApi { return DartToolingApiImpl.rpc(json_rpc_2.Peer.withoutJson(channel)); } - /// Connects the API over the provided WebSocket. - factory DartToolingApiImpl.webSocket(WebSocketChannel socket) { - return DartToolingApiImpl.rpc(json_rpc_2.Peer(socket.cast())); - } - final json_rpc_2.Peer _rpc; /// An API that provides Access to APIs related to VS Code, such as executing @@ -108,6 +103,7 @@ abstract base class ToolApiImpl { @protected Stream> events(String name) { final streamController = StreamController>.broadcast(); + unawaited(rpc.done.then((_) => streamController.close())); rpc.registerMethod('$apiName.$name', (json_rpc_2.Parameters parameters) { streamController.add(parameters.asMap.cast()); }); diff --git a/packages/devtools_app/lib/src/standalone_ui/api/impl/vs_code_api.dart b/packages/devtools_app/lib/src/standalone_ui/api/impl/vs_code_api.dart index 229415f25b9..fa6603782d0 100644 --- a/packages/devtools_app/lib/src/standalone_ui/api/impl/vs_code_api.dart +++ b/packages/devtools_app/lib/src/standalone_ui/api/impl/vs_code_api.dart @@ -11,23 +11,22 @@ import 'dart_tooling_api.dart'; final class VsCodeApiImpl extends ToolApiImpl implements VsCodeApi { VsCodeApiImpl._(super.rpc, Map capabilities) { this.capabilities = VsCodeCapabilitiesImpl(capabilities); - devicesChanged = - events('devicesChanged').map(VsCodeDevicesEventImpl.fromJson); + devicesChanged = events(VsCodeApi.jsonDevicesChangedEvent) + .map(VsCodeDevicesEventImpl.fromJson); } static Future tryConnect(json_rpc_2.Peer rpc) async { - final capabilities = await ToolApiImpl.tryGetCapabilities(rpc, _apiName); + final capabilities = + await ToolApiImpl.tryGetCapabilities(rpc, VsCodeApi.jsonApiName); return capabilities != null ? VsCodeApiImpl._(rpc, capabilities) : null; } - static const _apiName = 'vsCode'; - @override - Future initialize() => sendRequest('initialize'); + Future initialize() => sendRequest(VsCodeApi.jsonInitializeMethod); @override @protected - String get apiName => _apiName; + String get apiName => VsCodeApi.jsonApiName; @override late final Stream devicesChanged; @@ -38,16 +37,19 @@ final class VsCodeApiImpl extends ToolApiImpl implements VsCodeApi { @override Future executeCommand(String command, [List? arguments]) { return sendRequest( - 'executeCommand', - {'command': command, 'arguments': arguments}, + VsCodeApi.jsonExecuteCommandMethod, + { + VsCodeApi.jsonExecuteCommandCommandParameter: command, + VsCodeApi.jsonExecuteCommandArgumentsParameter: arguments, + }, ); } @override Future selectDevice(String id) { return sendRequest( - 'selectDevice', - {'id': id}, + VsCodeApi.jsonSelectDeviceMethod, + {VsCodeApi.jsonSelectDeviceIdParameter: id}, ); } } @@ -66,14 +68,14 @@ class VsCodeDeviceImpl implements VsCodeDevice { VsCodeDeviceImpl.fromJson(Map json) : this( - id: json['id'] as String, - name: json['name'] as String, - category: json['category'] as String?, - emulator: json['emulator'] as bool, - emulatorId: json['emulatorId'] as String?, - ephemeral: json['ephemeral'] as bool, - platform: json['platform'] as String, - platformType: json['platformType'] as String?, + id: json[VsCodeDevice.jsonIdField] as String, + name: json[VsCodeDevice.jsonNameField] as String, + category: json[VsCodeDevice.jsonCategoryField] as String?, + emulator: json[VsCodeDevice.jsonEmulatorField] as bool, + emulatorId: json[VsCodeDevice.jsonEmulatorIdField] as String?, + ephemeral: json[VsCodeDevice.jsonEphemeralField] as bool, + platform: json[VsCodeDevice.jsonPlatformField] as String, + platformType: json[VsCodeDevice.jsonPlatformTypeField] as String?, ); @override @@ -101,14 +103,14 @@ class VsCodeDeviceImpl implements VsCodeDevice { final String? platformType; Map toJson() => { - 'id': id, - 'name': name, - 'category': category, - 'emulator': emulator, - 'emulatorId': emulatorId, - 'ephemeral': ephemeral, - 'platform': platform, - 'platformType': platformType, + VsCodeDevice.jsonIdField: id, + VsCodeDevice.jsonNameField: name, + VsCodeDevice.jsonCategoryField: category, + VsCodeDevice.jsonEmulatorField: emulator, + VsCodeDevice.jsonEmulatorIdField: emulatorId, + VsCodeDevice.jsonEphemeralField: ephemeral, + VsCodeDevice.jsonPlatformField: platform, + VsCodeDevice.jsonPlatformTypeField: platformType, }; } @@ -120,8 +122,9 @@ class VsCodeDevicesEventImpl implements VsCodeDevicesEvent { VsCodeDevicesEventImpl.fromJson(Map json) : this( - selectedDeviceId: json['selectedDeviceId'] as String?, - devices: (json['devices'] as List) + selectedDeviceId: + json[VsCodeDevicesEvent.jsonSelectedDeviceIdField] as String?, + devices: (json[VsCodeDevicesEvent.jsonDevicesField] as List) .map((item) => Map.from(item)) .map((map) => VsCodeDeviceImpl.fromJson(map)) .toList(), @@ -134,8 +137,8 @@ class VsCodeDevicesEventImpl implements VsCodeDevicesEvent { final List devices; Map toJson() => { - 'selectedDeviceId': selectedDeviceId, - 'devices': devices, + VsCodeDevicesEvent.jsonSelectedDeviceIdField: selectedDeviceId, + VsCodeDevicesEvent.jsonDevicesField: devices, }; } @@ -145,5 +148,10 @@ class VsCodeCapabilitiesImpl implements VsCodeCapabilities { final Map? _raw; @override - bool get executeCommand => _raw?['executeCommand'] == true; + bool get executeCommand => + _raw?[VsCodeCapabilities.jsonExecuteCommandField] == true; + + @override + bool get selectDevice => + _raw?[VsCodeCapabilities.jsonSelectDeviceField] == true; } diff --git a/packages/devtools_app/lib/src/standalone_ui/api/vs_code_api.dart b/packages/devtools_app/lib/src/standalone_ui/api/vs_code_api.dart index 4872d82ec1e..d3d8d206700 100644 --- a/packages/devtools_app/lib/src/standalone_ui/api/vs_code_api.dart +++ b/packages/devtools_app/lib/src/standalone_ui/api/vs_code_api.dart @@ -14,8 +14,27 @@ abstract interface class VsCodeApi { Stream get devicesChanged; Future executeCommand(String command, [List? arguments]); Future selectDevice(String id); + + static const jsonApiName = 'vsCode'; + + static const jsonInitializeMethod = 'initialize'; + + static const jsonExecuteCommandMethod = 'executeCommand'; + static const jsonExecuteCommandCommandParameter = 'command'; + static const jsonExecuteCommandArgumentsParameter = 'arguments'; + + static const jsonDevicesChangedEvent = 'devicesChanged'; + + static const jsonSelectDeviceMethod = 'selectDevice'; + static const jsonSelectDeviceIdParameter = 'id'; } +/// This class defines a device exposed by the Dart/Flutter extensions in VS +/// Code (and must match the implementation there). +/// +/// All changes to this file should be backwards-compatible and use +/// [VsCodeCapabilities] to advertise which capabilities are available and +/// handle any changes in behaviour. abstract interface class VsCodeDevice { String get id; String get name; @@ -25,13 +44,41 @@ abstract interface class VsCodeDevice { bool get ephemeral; String get platform; String? get platformType; + + static const jsonIdField = 'id'; + static const jsonNameField = 'name'; + static const jsonCategoryField = 'category'; + static const jsonEmulatorField = 'emulator'; + static const jsonEmulatorIdField = 'emulatorId'; + static const jsonEphemeralField = 'ephemeral'; + static const jsonPlatformField = 'platform'; + static const jsonPlatformTypeField = 'platformType'; } +/// This class defines a device event sent by the Dart/Flutter extensions in VS +/// Code (and must match the implementation there). +/// +/// All changes to this file should be backwards-compatible and use +/// [VsCodeCapabilities] to advertise which capabilities are available and +/// handle any changes in behaviour. abstract interface class VsCodeDevicesEvent { String? get selectedDeviceId; List get devices; + + static const jsonSelectedDeviceIdField = 'selectedDeviceId'; + static const jsonDevicesField = 'devices'; } +/// This class defines the capabilities provided by the current version of the +/// Dart/Flutter extensions in VS Code. +/// +/// All changes to this file should be backwards-compatible and use +/// [VsCodeCapabilities] to advertise which capabilities are available and +/// handle any changes in behaviour. abstract interface class VsCodeCapabilities { bool get executeCommand; + bool get selectDevice; + + static const jsonExecuteCommandField = 'executeCommand'; + static const jsonSelectDeviceField = 'selectDevice'; } diff --git a/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart b/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart index 60b5691411e..5225fc324ca 100644 --- a/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart +++ b/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart @@ -15,25 +15,17 @@ import '../api/vs_code_api.dart'; /// /// Provides some basic functionality to improve discoverability of features /// such as creation of new projects, device selection and DevTools features. -class VsCodeFlutterPanel extends StatefulWidget { +class VsCodeFlutterPanel extends StatelessWidget { const VsCodeFlutterPanel(this.api, {super.key}); final DartToolingApi api; - @override - State createState() => _VsCodeFlutterPanelState(); -} - -class _VsCodeFlutterPanelState extends State { @override Widget build(BuildContext context) { assert(FeatureFlags.vsCodeSidebarTooling); - final api = widget.api; - return Column( children: [ - const Text(''), FutureBuilder( future: api.vsCode, builder: (context, snapshot) => @@ -73,41 +65,43 @@ class _VsCodeConnectedPanelState extends State<_VsCodeConnectedPanel> { Widget build(BuildContext context) { return Column( children: [ + const SizedBox(height: 20), if (widget.api.capabilities.executeCommand) ElevatedButton( onPressed: () => unawaited(widget.api.executeCommand('flutter.createProject')), child: const Text('New Flutter Project'), ), - StreamBuilder( - stream: widget.api.devicesChanged, - builder: (context, snapshot) { - if (!snapshot.hasData) { - return const Text(''); - } - final deviceEvent = snapshot.data!; - return Table( - defaultVerticalAlignment: TableCellVerticalAlignment.middle, - children: [ - for (final device in deviceEvent.devices) - TableRow( - children: [ - TextButton( - child: Text(device.name), - onPressed: () => - unawaited(widget.api.selectDevice(device.id)), - ), - Text( - device.id == deviceEvent.selectedDeviceId - ? '(selected)' - : '', - ), - ], - ), - ], - ); - }, - ), + if (widget.api.capabilities.selectDevice) + StreamBuilder( + stream: widget.api.devicesChanged, + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const SizedBox.shrink(); + } + final deviceEvent = snapshot.data!; + return Table( + defaultVerticalAlignment: TableCellVerticalAlignment.middle, + children: [ + for (final device in deviceEvent.devices) + TableRow( + children: [ + TextButton( + child: Text(device.name), + onPressed: () => + unawaited(widget.api.selectDevice(device.id)), + ), + Text( + device.id == deviceEvent.selectedDeviceId + ? '(selected)' + : '', + ), + ], + ), + ], + ); + }, + ), if (widget.api.capabilities.executeCommand) ElevatedButton( onPressed: () => diff --git a/packages/devtools_app/lib/src/standalone_ui/vs_code/mock_environment/dart_tooling_mock_api.dart b/packages/devtools_app/lib/src/standalone_ui/vs_code/mock_environment/dart_tooling_mock_api.dart index 3bd50ae4567..63f8d1c9df0 100644 --- a/packages/devtools_app/lib/src/standalone_ui/vs_code/mock_environment/dart_tooling_mock_api.dart +++ b/packages/devtools_app/lib/src/standalone_ui/vs_code/mock_environment/dart_tooling_mock_api.dart @@ -65,6 +65,7 @@ class MockDartToolingApi extends DartToolingApiImpl { server.registerMethod('vsCode.getCapabilities', () async { return { 'executeCommand': true, + 'selectDevice': true, }; }); server.registerMethod('vsCode.initialize', initialize); @@ -84,7 +85,7 @@ class MockDartToolingApi extends DartToolingApiImpl { emulator: false, emulatorId: null, ephemeral: false, - platform: 'dartwin-x64', + platform: 'darwin-x64', platformType: 'macos', ), VsCodeDeviceImpl( @@ -100,7 +101,7 @@ class MockDartToolingApi extends DartToolingApiImpl { ]; /// The current set of devices being presented to the embedded panel. - final List _devices = []; + final _devices = []; /// The currently selected device presented to the embedded panel. String? _selectedDeviceId; From 9d90cccb235176d06720bf59da0229d5f0937e4d Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Mon, 7 Aug 2023 15:52:26 +0100 Subject: [PATCH 4/8] Move mock environment to test/stager scene --- packages/.vscode/launch.json | 14 +-- .../src/standalone_ui/standalone_screen.dart | 6 +- .../scenes/standalone_ui/vs_code.dart | 40 ++++++++ .../standalone_ui/vs_code_mock_editor.dart} | 94 ++++++++++++++++--- .../test_data/dart_tooling_api/mock_api.dart} | 13 +-- 5 files changed, 133 insertions(+), 34 deletions(-) create mode 100644 packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code.dart rename packages/devtools_app/{lib/src/standalone_ui/vs_code/mock_environment/flutter_panel_mock_editor.dart => test/test_infra/scenes/standalone_ui/vs_code_mock_editor.dart} (63%) rename packages/devtools_app/{lib/src/standalone_ui/vs_code/mock_environment/dart_tooling_mock_api.dart => test/test_infra/test_data/dart_tooling_api/mock_api.dart} (93%) diff --git a/packages/.vscode/launch.json b/packages/.vscode/launch.json index 6c3a9dbb39c..a41186fca1d 100644 --- a/packages/.vscode/launch.json +++ b/packages/.vscode/launch.json @@ -79,19 +79,11 @@ "program": "devtools_app/test/test_infra/fixtures/memory_app/lib/main.dart", }, { - "name": "VS Code Sidebar Environment", + "name": "standalone_ui/vs_code", "request": "launch", "type": "dart", - "program": "devtools_app/lib/main.dart", - "args": [ - "--dart-define=enable_experiments=true", - // web-launch-url only works with an absolute URL so we must set hostname/port - // to match. However, this is convenient for using `dart.customDevToolsUri` for - // testing inside VS Code anyway. - "--web-hostname=localhost", - "--web-port=8765", - "--web-launch-url=http://localhost:8765/vsCodeFlutterPanelMockEditor", - ], + "program": "devtools_app/test/test_infra/scenes/standalone_ui/vs_code.stager_app.g.dart", + "deviceId": "chrome", }, { "name": "attach", diff --git a/packages/devtools_app/lib/src/standalone_ui/standalone_screen.dart b/packages/devtools_app/lib/src/standalone_ui/standalone_screen.dart index 9dec5f00233..7bc7d62e737 100644 --- a/packages/devtools_app/lib/src/standalone_ui/standalone_screen.dart +++ b/packages/devtools_app/lib/src/standalone_ui/standalone_screen.dart @@ -6,7 +6,6 @@ import 'package:flutter/material.dart'; import 'api/impl/dart_tooling_api.dart'; import 'vs_code/flutter_panel.dart'; -import 'vs_code/mock_environment/flutter_panel_mock_editor.dart'; /// "Screens" that are intended for standalone use only, likely for embedding /// directly in an IDE. @@ -15,8 +14,7 @@ import 'vs_code/mock_environment/flutter_panel_mock_editor.dart'; /// meaning that this screen will not be part of DevTools' normal navigation. /// The only way to access a standalone screen is directly from the url. enum StandaloneScreenType { - vsCodeFlutterPanel, - vsCodeFlutterPanelMockEditor; + vsCodeFlutterPanel; static StandaloneScreenType? parse(String? id) { if (id == null) return null; @@ -31,8 +29,6 @@ enum StandaloneScreenType { return switch (this) { StandaloneScreenType.vsCodeFlutterPanel => VsCodeFlutterPanel(DartToolingApiImpl.postMessage()), - StandaloneScreenType.vsCodeFlutterPanelMockEditor => - const VsCodeFlutterPanelMockEditor(), }; } } diff --git a/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code.dart b/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code.dart new file mode 100644 index 00000000000..db89c034844 --- /dev/null +++ b/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code.dart @@ -0,0 +1,40 @@ +// Copyright 2022 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. + +import 'package:devtools_app/devtools_app.dart'; +import 'package:devtools_app/src/shared/feature_flags.dart'; +import 'package:devtools_app/src/standalone_ui/vs_code/flutter_panel.dart'; +import 'package:flutter/material.dart'; +import 'package:stager/stager.dart'; + +import '../../test_data/dart_tooling_api/mock_api.dart'; +import 'vs_code_mock_editor.dart'; + +final _api = MockDartToolingApi(); + +/// To run, use the "standalone_ui/vs_code" launch configuration with the +/// `devtools/packages/` folder open in VS Code, or run: +/// +/// flutter run -t test/test_infra/scenes/standalone_ui/vs_code.stager_app.g.dart --dart-define=enable_experiments=true -d chrome +class VsCodeScene extends Scene { + late PerformanceController controller; + + @override + Widget build(BuildContext context) { + return VsCodeFlutterPanelMockEditor( + api: _api, + child: VsCodeFlutterPanel(_api), + ); + } + + @override + String get title => '$VsCodeScene'; + + @override + Future setUp() async { + FeatureFlags.vsCodeSidebarTooling = true; + + setGlobal(IdeTheme, IdeTheme()); + } +} diff --git a/packages/devtools_app/lib/src/standalone_ui/vs_code/mock_environment/flutter_panel_mock_editor.dart b/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code_mock_editor.dart similarity index 63% rename from packages/devtools_app/lib/src/standalone_ui/vs_code/mock_environment/flutter_panel_mock_editor.dart rename to packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code_mock_editor.dart index 277961edcec..02f327974b9 100644 --- a/packages/devtools_app/lib/src/standalone_ui/vs_code/mock_environment/flutter_panel_mock_editor.dart +++ b/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code_mock_editor.dart @@ -5,12 +5,11 @@ import 'dart:collection'; import 'dart:convert'; +import 'package:devtools_app/src/shared/split.dart'; +import 'package:devtools_app/src/shared/theme.dart'; import 'package:flutter/material.dart'; -import '../../../shared/split.dart'; -import '../../../shared/theme.dart'; -import '../flutter_panel.dart'; -import 'dart_tooling_mock_api.dart'; +import '../../test_data/dart_tooling_api/mock_api.dart'; /// A simple UI that acts as a stand-in host IDE to simplify the development /// workflow when working on embedded tooling. @@ -18,7 +17,16 @@ import 'dart_tooling_mock_api.dart'; /// This UI interacts with [MockDartToolingApi] to allow triggering events that /// would normally be fired by the IDE and also shows a log of recent requests. class VsCodeFlutterPanelMockEditor extends StatefulWidget { - const VsCodeFlutterPanelMockEditor({super.key}); + const VsCodeFlutterPanelMockEditor({ + super.key, + required this.api, + this.child, + }); + + /// The mock API to interact with. + final MockDartToolingApi api; + + final Widget? child; @override State createState() => @@ -27,8 +35,7 @@ class VsCodeFlutterPanelMockEditor extends StatefulWidget { class _VsCodeFlutterPanelMockEditorState extends State { - /// The mock API to interact with. - final api = MockDartToolingApi(); + MockDartToolingApi get api => widget.api; /// The number of communication messages to keep in the log. static const maxLogEvents = 20; @@ -61,10 +68,20 @@ class _VsCodeFlutterPanelMockEditorState @override Widget build(BuildContext context) { + final editorTheme = VsCodeTheme.of(context); return Split( axis: Axis.horizontal, - initialFractions: const [0.2, 0.8], + initialFractions: const [0.25, 0.75], minSizes: const [200, 200], + splitters: [ + PreferredSize( + preferredSize: const Size.fromWidth(1), + child: SizedBox( + width: 1, + child: ColoredBox(color: editorTheme.editorSidebarSplitterColor), + ), + ), + ], children: [ Row( children: [ @@ -74,20 +91,35 @@ class _VsCodeFlutterPanelMockEditorState alignment: Alignment.topCenter, padding: const EdgeInsets.only(top: 60), constraints: const BoxConstraints.expand(width: 48), - color: const Color(0xFF333333), + color: editorTheme.activityBarBackgroundColor, child: Image.memory(sidebarImageBytes), ), ), - Expanded(child: VsCodeFlutterPanel(api)), + Expanded( + child: Container( + color: editorTheme.sidebarBackgroundColor, + child: widget.child ?? const Placeholder(), + ), + ), ], ), Split( axis: Axis.vertical, initialFractions: const [0.5, 0.5], minSizes: const [200, 200], + splitters: [ + PreferredSize( + preferredSize: const Size.fromHeight(1), + child: SizedBox( + height: 1, + child: + ColoredBox(color: editorTheme.editorTerminalSplitterColor), + ), + ), + ], children: [ Container( - color: const Color(0xFF282828), + color: editorTheme.editorBackgroundColor, padding: const EdgeInsets.all(10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -117,7 +149,7 @@ class _VsCodeFlutterPanelMockEditorState ), ), Container( - color: const Color(0xFF222222), + color: editorTheme.editorBackgroundColor, padding: const EdgeInsets.all(10), child: StreamBuilder( stream: logUpdated, @@ -141,3 +173,41 @@ class _VsCodeFlutterPanelMockEditorState ); } } + +/// A basic theme that matches the default colours of VS Code dart/light themes +/// so the mock environment can be displayed in either. +class VsCodeTheme { + const VsCodeTheme._({ + required this.activityBarBackgroundColor, + required this.editorBackgroundColor, + required this.editorSidebarSplitterColor, + required this.editorTerminalSplitterColor, + required this.sidebarBackgroundColor, + }); + + static VsCodeTheme of(BuildContext context) { + // TODO(dantup): Figure out why Theme.of(context).isDarkTheme doesn't + // match what's in the stager sidebar. + return Theme.of(context).isDarkTheme + ? const VsCodeTheme._( + activityBarBackgroundColor: Color(0xFF333333), + editorBackgroundColor: Color(0xFF1E1E1E), + editorSidebarSplitterColor: Color(0xFF252526), + editorTerminalSplitterColor: Color(0xFF414141), + sidebarBackgroundColor: Color(0xFF252526), + ) + : const VsCodeTheme._( + activityBarBackgroundColor: Color(0xFF2C2C2C), + editorBackgroundColor: Color(0xFFFFFFFF), + editorSidebarSplitterColor: Color(0xFFF3F3F3), + editorTerminalSplitterColor: Color(0xFFC4C4C4), + sidebarBackgroundColor: Color(0xFFF3F3F3), + ); + } + + final Color activityBarBackgroundColor; + final Color editorBackgroundColor; + final Color editorSidebarSplitterColor; + final Color editorTerminalSplitterColor; + final Color sidebarBackgroundColor; +} diff --git a/packages/devtools_app/lib/src/standalone_ui/vs_code/mock_environment/dart_tooling_mock_api.dart b/packages/devtools_app/test/test_infra/test_data/dart_tooling_api/mock_api.dart similarity index 93% rename from packages/devtools_app/lib/src/standalone_ui/vs_code/mock_environment/dart_tooling_mock_api.dart rename to packages/devtools_app/test/test_infra/test_data/dart_tooling_api/mock_api.dart index 63f8d1c9df0..3acf15b9509 100644 --- a/packages/devtools_app/lib/src/standalone_ui/vs_code/mock_environment/dart_tooling_mock_api.dart +++ b/packages/devtools_app/test/test_infra/test_data/dart_tooling_api/mock_api.dart @@ -4,15 +4,14 @@ import 'dart:async'; +import 'package:devtools_app/src/standalone_ui/api/impl/dart_tooling_api.dart'; +import 'package:devtools_app/src/standalone_ui/api/impl/vs_code_api.dart'; +import 'package:devtools_app/src/standalone_ui/api/vs_code_api.dart'; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc_2; import 'package:stream_channel/stream_channel.dart'; -import '../../api/impl/dart_tooling_api.dart'; -import '../../api/impl/vs_code_api.dart'; -import '../../api/vs_code_api.dart'; - -/// A [DartToolingApi] that acts as a stand-in host IDE to simplify the development -/// workflow when working on embedded tooling. +/// A [DartToolingApi] that acts as a stand-in host IDE to simplify the +/// development workflow when working on embedded tooling. /// /// This API will handle requests with canned responses and can generate /// events in a similar way to the IDE would. It is used by @@ -121,6 +120,8 @@ class MockDartToolingApi extends DartToolingApiImpl { switch (command) { case 'flutter.createProject': return null; + case 'flutter.doctor': + return null; default: throw 'Unknown command $command'; } From e560948d6b843964a9e390efcc48e7c510d1ffed Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Tue, 8 Aug 2023 15:50:33 +0100 Subject: [PATCH 5/8] Fix "Dark Theme" toggle in VS Code panel in stager scene --- .../scenes/standalone_ui/vs_code.dart | 36 ++++++++++++---- .../standalone_ui/vs_code_mock_editor.dart | 42 +++++++++++-------- 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code.dart b/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code.dart index db89c034844..c61e713f93a 100644 --- a/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code.dart +++ b/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code.dart @@ -1,14 +1,15 @@ -// Copyright 2022 The Chromium Authors. All rights reserved. +// 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. import 'package:devtools_app/devtools_app.dart'; +import 'package:devtools_app/src/framework/scaffold.dart'; import 'package:devtools_app/src/shared/feature_flags.dart'; import 'package:devtools_app/src/standalone_ui/vs_code/flutter_panel.dart'; import 'package:flutter/material.dart'; import 'package:stager/stager.dart'; -import '../../test_data/dart_tooling_api/mock_api.dart'; +import '../../../test_infra/test_data/dart_tooling_api/mock_api.dart'; import 'vs_code_mock_editor.dart'; final _api = MockDartToolingApi(); @@ -22,9 +23,32 @@ class VsCodeScene extends Scene { @override Widget build(BuildContext context) { - return VsCodeFlutterPanelMockEditor( - api: _api, - child: VsCodeFlutterPanel(_api), + return MaterialApp( + theme: themeFor( + isDarkTheme: false, + ideTheme: _ideTheme(const VsCodeTheme.light()), + theme: ThemeData(useMaterial3: true, colorScheme: lightColorScheme), + ), + darkTheme: themeFor( + isDarkTheme: true, + ideTheme: _ideTheme(const VsCodeTheme.dark()), + theme: ThemeData(useMaterial3: true, colorScheme: darkColorScheme), + ), + home: Scaffold( + body: VsCodeFlutterPanelMockEditor( + api: _api, + child: VsCodeFlutterPanel(_api), + ), + ), + ); + } + + /// Creates an [IdeTheme] using the colours from the mock editor. + IdeTheme _ideTheme(VsCodeTheme vsCodeTheme) { + return IdeTheme( + backgroundColor: vsCodeTheme.editorBackgroundColor, + foregroundColor: vsCodeTheme.foregroundColor, + embed: true, ); } @@ -34,7 +58,5 @@ class VsCodeScene extends Scene { @override Future setUp() async { FeatureFlags.vsCodeSidebarTooling = true; - - setGlobal(IdeTheme, IdeTheme()); } } diff --git a/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code_mock_editor.dart b/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code_mock_editor.dart index 02f327974b9..3d7fa8c3bde 100644 --- a/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code_mock_editor.dart +++ b/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code_mock_editor.dart @@ -9,7 +9,7 @@ import 'package:devtools_app/src/shared/split.dart'; import 'package:devtools_app/src/shared/theme.dart'; import 'package:flutter/material.dart'; -import '../../test_data/dart_tooling_api/mock_api.dart'; +import '../../../test_infra/test_data/dart_tooling_api/mock_api.dart'; /// A simple UI that acts as a stand-in host IDE to simplify the development /// workflow when working on embedded tooling. @@ -182,32 +182,40 @@ class VsCodeTheme { required this.editorBackgroundColor, required this.editorSidebarSplitterColor, required this.editorTerminalSplitterColor, + required this.foregroundColor, required this.sidebarBackgroundColor, }); + const VsCodeTheme.dark() + : this._( + activityBarBackgroundColor: const Color(0xFF333333), + editorBackgroundColor: const Color(0xFF1E1E1E), + editorSidebarSplitterColor: const Color(0xFF252526), + editorTerminalSplitterColor: const Color(0xFF414141), + foregroundColor: const Color(0xFFD4D4D4), + sidebarBackgroundColor: const Color(0xFF252526), + ); + + const VsCodeTheme.light() + : this._( + activityBarBackgroundColor: const Color(0xFF2C2C2C), + editorBackgroundColor: const Color(0xFFFFFFFF), + editorSidebarSplitterColor: const Color(0xFFF3F3F3), + editorTerminalSplitterColor: const Color(0xFFC4C4C4), + foregroundColor: const Color(0xFF000000), + sidebarBackgroundColor: const Color(0xFFF3F3F3), + ); + static VsCodeTheme of(BuildContext context) { - // TODO(dantup): Figure out why Theme.of(context).isDarkTheme doesn't - // match what's in the stager sidebar. return Theme.of(context).isDarkTheme - ? const VsCodeTheme._( - activityBarBackgroundColor: Color(0xFF333333), - editorBackgroundColor: Color(0xFF1E1E1E), - editorSidebarSplitterColor: Color(0xFF252526), - editorTerminalSplitterColor: Color(0xFF414141), - sidebarBackgroundColor: Color(0xFF252526), - ) - : const VsCodeTheme._( - activityBarBackgroundColor: Color(0xFF2C2C2C), - editorBackgroundColor: Color(0xFFFFFFFF), - editorSidebarSplitterColor: Color(0xFFF3F3F3), - editorTerminalSplitterColor: Color(0xFFC4C4C4), - sidebarBackgroundColor: Color(0xFFF3F3F3), - ); + ? const VsCodeTheme.dark() + : const VsCodeTheme.light(); } final Color activityBarBackgroundColor; final Color editorBackgroundColor; final Color editorSidebarSplitterColor; final Color editorTerminalSplitterColor; + final Color foregroundColor; final Color sidebarBackgroundColor; } From b32f765e396275d8a848a56a15133abb5656d1cb Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Sun, 13 Aug 2023 12:47:03 +0100 Subject: [PATCH 6/8] Use defaultSpacing + remove custom split widgets --- .../standalone_ui/vs_code/flutter_panel.dart | 2 +- .../scenes/standalone_ui/vs_code.dart | 1 - .../standalone_ui/vs_code_mock_editor.dart | 27 ------------------- 3 files changed, 1 insertion(+), 29 deletions(-) diff --git a/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart b/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart index 5225fc324ca..7a1a11ff146 100644 --- a/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart +++ b/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart @@ -65,7 +65,7 @@ class _VsCodeConnectedPanelState extends State<_VsCodeConnectedPanel> { Widget build(BuildContext context) { return Column( children: [ - const SizedBox(height: 20), + const SizedBox(height: defaultSpacing), if (widget.api.capabilities.executeCommand) ElevatedButton( onPressed: () => diff --git a/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code.dart b/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code.dart index c61e713f93a..8a56009efe0 100644 --- a/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code.dart +++ b/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code.dart @@ -3,7 +3,6 @@ // in the LICENSE file. import 'package:devtools_app/devtools_app.dart'; -import 'package:devtools_app/src/framework/scaffold.dart'; import 'package:devtools_app/src/shared/feature_flags.dart'; import 'package:devtools_app/src/standalone_ui/vs_code/flutter_panel.dart'; import 'package:flutter/material.dart'; diff --git a/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code_mock_editor.dart b/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code_mock_editor.dart index 3d7fa8c3bde..ab3ddfbb3c3 100644 --- a/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code_mock_editor.dart +++ b/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code_mock_editor.dart @@ -73,15 +73,6 @@ class _VsCodeFlutterPanelMockEditorState axis: Axis.horizontal, initialFractions: const [0.25, 0.75], minSizes: const [200, 200], - splitters: [ - PreferredSize( - preferredSize: const Size.fromWidth(1), - child: SizedBox( - width: 1, - child: ColoredBox(color: editorTheme.editorSidebarSplitterColor), - ), - ), - ], children: [ Row( children: [ @@ -107,16 +98,6 @@ class _VsCodeFlutterPanelMockEditorState axis: Axis.vertical, initialFractions: const [0.5, 0.5], minSizes: const [200, 200], - splitters: [ - PreferredSize( - preferredSize: const Size.fromHeight(1), - child: SizedBox( - height: 1, - child: - ColoredBox(color: editorTheme.editorTerminalSplitterColor), - ), - ), - ], children: [ Container( color: editorTheme.editorBackgroundColor, @@ -180,8 +161,6 @@ class VsCodeTheme { const VsCodeTheme._({ required this.activityBarBackgroundColor, required this.editorBackgroundColor, - required this.editorSidebarSplitterColor, - required this.editorTerminalSplitterColor, required this.foregroundColor, required this.sidebarBackgroundColor, }); @@ -190,8 +169,6 @@ class VsCodeTheme { : this._( activityBarBackgroundColor: const Color(0xFF333333), editorBackgroundColor: const Color(0xFF1E1E1E), - editorSidebarSplitterColor: const Color(0xFF252526), - editorTerminalSplitterColor: const Color(0xFF414141), foregroundColor: const Color(0xFFD4D4D4), sidebarBackgroundColor: const Color(0xFF252526), ); @@ -200,8 +177,6 @@ class VsCodeTheme { : this._( activityBarBackgroundColor: const Color(0xFF2C2C2C), editorBackgroundColor: const Color(0xFFFFFFFF), - editorSidebarSplitterColor: const Color(0xFFF3F3F3), - editorTerminalSplitterColor: const Color(0xFFC4C4C4), foregroundColor: const Color(0xFF000000), sidebarBackgroundColor: const Color(0xFFF3F3F3), ); @@ -214,8 +189,6 @@ class VsCodeTheme { final Color activityBarBackgroundColor; final Color editorBackgroundColor; - final Color editorSidebarSplitterColor; - final Color editorTerminalSplitterColor; final Color foregroundColor; final Color sidebarBackgroundColor; } From 7aaf25f37f946e208b06c13f7305ad7888062342 Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Sun, 13 Aug 2023 13:04:54 +0100 Subject: [PATCH 7/8] Remove unused key parameter --- .../lib/src/standalone_ui/vs_code/flutter_panel.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart b/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart index 7a1a11ff146..3026885bded 100644 --- a/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart +++ b/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart @@ -45,7 +45,7 @@ class VsCodeFlutterPanel extends StatelessWidget { /// The panel shown once we know VS Code is available (the host has responded to /// the `vsCode.getCapabilities` request). class _VsCodeConnectedPanel extends StatefulWidget { - const _VsCodeConnectedPanel(this.api, {super.key}); + const _VsCodeConnectedPanel(this.api); final VsCodeApi api; From 074eb478f050df5ecba4a69652627c50582f9b5b Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Mon, 21 Aug 2023 10:25:32 +0100 Subject: [PATCH 8/8] Update imports --- .../lib/src/standalone_ui/vs_code/flutter_panel.dart | 1 + .../test/test_infra/scenes/standalone_ui/vs_code.dart | 1 + .../test_infra/scenes/standalone_ui/vs_code_mock_editor.dart | 3 +-- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart b/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart index 3026885bded..741ccd6c5a8 100644 --- a/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart +++ b/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart @@ -4,6 +4,7 @@ import 'dart:async'; +import 'package:devtools_app_shared/ui.dart'; import 'package:flutter/material.dart'; import '../../../devtools_app.dart'; diff --git a/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code.dart b/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code.dart index 8a56009efe0..50ed69336e9 100644 --- a/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code.dart +++ b/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code.dart @@ -5,6 +5,7 @@ import 'package:devtools_app/devtools_app.dart'; import 'package:devtools_app/src/shared/feature_flags.dart'; import 'package:devtools_app/src/standalone_ui/vs_code/flutter_panel.dart'; +import 'package:devtools_app_shared/ui.dart'; import 'package:flutter/material.dart'; import 'package:stager/stager.dart'; diff --git a/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code_mock_editor.dart b/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code_mock_editor.dart index ab3ddfbb3c3..b71fd3924fb 100644 --- a/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code_mock_editor.dart +++ b/packages/devtools_app/test/test_infra/scenes/standalone_ui/vs_code_mock_editor.dart @@ -5,8 +5,7 @@ import 'dart:collection'; import 'dart:convert'; -import 'package:devtools_app/src/shared/split.dart'; -import 'package:devtools_app/src/shared/theme.dart'; +import 'package:devtools_app_shared/ui.dart'; import 'package:flutter/material.dart'; import '../../../test_infra/test_data/dart_tooling_api/mock_api.dart';