-
Notifications
You must be signed in to change notification settings - Fork 326
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add some simple side bar functionality with a mock editor environment (…
…#6104) * Add some simple side bar functionality with a mock editor environment * Add additional comments * Minor review comments - Add comments - Use more constants - StatelessWidget where state not required - Fix typos - Add `selectDevice` capability - ... * Move mock environment to test/stager scene * Fix "Dark Theme" toggle in VS Code panel in stager scene * Use defaultSpacing + remove custom split widgets * Remove unused key parameter * Update imports
- Loading branch information
Showing
14 changed files
with
948 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
15
packages/devtools_app/lib/src/shared/config_specific/post_message/post_message.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |
11 changes: 11 additions & 0 deletions
11
packages/devtools_app/lib/src/shared/config_specific/post_message/post_message_stub.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<PostMessageEvent> get onPostMessage => | ||
throw UnsupportedError('unsupported platform'); | ||
|
||
void postMessage(Object? _, String __) => | ||
throw UnsupportedError('unsupported platform'); |
19 changes: 19 additions & 0 deletions
19
packages/devtools_app/lib/src/shared/config_specific/post_message/post_message_web.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<PostMessageEvent> 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); |
17 changes: 17 additions & 0 deletions
17
packages/devtools_app/lib/src/standalone_ui/api/dart_tooling_api.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// 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'; | ||
|
||
/// 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 | ||
/// 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<VsCodeApi?> get vsCode; | ||
} |
112 changes: 112 additions & 0 deletions
112
packages/devtools_app/lib/src/standalone_ui/api/impl/dart_tooling_api.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// 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 '../../../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'; | ||
|
||
/// 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; | ||
|
||
final _log = Logger('tooling_api'); | ||
|
||
/// 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()); | ||
} | ||
|
||
/// Connects the API using 'postMessage'. This is only available when running | ||
/// on web and hosted inside an iframe (such as inside a VS Code webview). | ||
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)); | ||
} | ||
|
||
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<VsCodeApi?> 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<Map<String, Object?>?> tryGetCapabilities( | ||
json_rpc_2.Peer rpc, | ||
String apiName, | ||
) async { | ||
try { | ||
final response = await rpc.sendRequest('$apiName.getCapabilities') | ||
as Map<Object?, Object?>; | ||
return response.cast<String, Object?>(); | ||
} catch (_) { | ||
// Any error initializing should disable this functionality. | ||
return null; | ||
} | ||
} | ||
|
||
@protected | ||
final json_rpc_2.Peer rpc; | ||
|
||
@protected | ||
String get apiName; | ||
|
||
@protected | ||
Future<T> sendRequest<T>(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<Map<String, Object?>> events(String name) { | ||
final streamController = StreamController<Map<String, Object?>>.broadcast(); | ||
unawaited(rpc.done.then((_) => streamController.close())); | ||
rpc.registerMethod('$apiName.$name', (json_rpc_2.Parameters parameters) { | ||
streamController.add(parameters.asMap.cast<String, Object?>()); | ||
}); | ||
return streamController.stream; | ||
} | ||
} |
157 changes: 157 additions & 0 deletions
157
packages/devtools_app/lib/src/standalone_ui/api/impl/vs_code_api.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
// 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<String, Object?> capabilities) { | ||
this.capabilities = VsCodeCapabilitiesImpl(capabilities); | ||
devicesChanged = events(VsCodeApi.jsonDevicesChangedEvent) | ||
.map(VsCodeDevicesEventImpl.fromJson); | ||
} | ||
|
||
static Future<VsCodeApi?> tryConnect(json_rpc_2.Peer rpc) async { | ||
final capabilities = | ||
await ToolApiImpl.tryGetCapabilities(rpc, VsCodeApi.jsonApiName); | ||
return capabilities != null ? VsCodeApiImpl._(rpc, capabilities) : null; | ||
} | ||
|
||
@override | ||
Future<void> initialize() => sendRequest(VsCodeApi.jsonInitializeMethod); | ||
|
||
@override | ||
@protected | ||
String get apiName => VsCodeApi.jsonApiName; | ||
|
||
@override | ||
late final Stream<VsCodeDevicesEvent> devicesChanged; | ||
|
||
@override | ||
late final VsCodeCapabilities capabilities; | ||
|
||
@override | ||
Future<Object?> executeCommand(String command, [List<Object?>? arguments]) { | ||
return sendRequest( | ||
VsCodeApi.jsonExecuteCommandMethod, | ||
{ | ||
VsCodeApi.jsonExecuteCommandCommandParameter: command, | ||
VsCodeApi.jsonExecuteCommandArgumentsParameter: arguments, | ||
}, | ||
); | ||
} | ||
|
||
@override | ||
Future<bool> selectDevice(String id) { | ||
return sendRequest( | ||
VsCodeApi.jsonSelectDeviceMethod, | ||
{VsCodeApi.jsonSelectDeviceIdParameter: 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<String, Object?> json) | ||
: this( | ||
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 | ||
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<String, Object?> toJson() => { | ||
VsCodeDevice.jsonIdField: id, | ||
VsCodeDevice.jsonNameField: name, | ||
VsCodeDevice.jsonCategoryField: category, | ||
VsCodeDevice.jsonEmulatorField: emulator, | ||
VsCodeDevice.jsonEmulatorIdField: emulatorId, | ||
VsCodeDevice.jsonEphemeralField: ephemeral, | ||
VsCodeDevice.jsonPlatformField: platform, | ||
VsCodeDevice.jsonPlatformTypeField: platformType, | ||
}; | ||
} | ||
|
||
class VsCodeDevicesEventImpl implements VsCodeDevicesEvent { | ||
VsCodeDevicesEventImpl({ | ||
required this.selectedDeviceId, | ||
required this.devices, | ||
}); | ||
|
||
VsCodeDevicesEventImpl.fromJson(Map<String, Object?> json) | ||
: this( | ||
selectedDeviceId: | ||
json[VsCodeDevicesEvent.jsonSelectedDeviceIdField] as String?, | ||
devices: (json[VsCodeDevicesEvent.jsonDevicesField] as List) | ||
.map((item) => Map<String, Object?>.from(item)) | ||
.map((map) => VsCodeDeviceImpl.fromJson(map)) | ||
.toList(), | ||
); | ||
|
||
@override | ||
final String? selectedDeviceId; | ||
|
||
@override | ||
final List<VsCodeDevice> devices; | ||
|
||
Map<String, Object?> toJson() => { | ||
VsCodeDevicesEvent.jsonSelectedDeviceIdField: selectedDeviceId, | ||
VsCodeDevicesEvent.jsonDevicesField: devices, | ||
}; | ||
} | ||
|
||
class VsCodeCapabilitiesImpl implements VsCodeCapabilities { | ||
VsCodeCapabilitiesImpl(this._raw); | ||
|
||
final Map<String, Object?>? _raw; | ||
|
||
@override | ||
bool get executeCommand => | ||
_raw?[VsCodeCapabilities.jsonExecuteCommandField] == true; | ||
|
||
@override | ||
bool get selectDevice => | ||
_raw?[VsCodeCapabilities.jsonSelectDeviceField] == true; | ||
} |
Oops, something went wrong.