// Copyright 2014 The Flutter 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:dtd/dtd.dart'; import 'package:json_rpc_2/json_rpc_2.dart'; import 'package:widget_preview_scaffold/src/dtd/dtd_connection_info.dart'; import 'package:widget_preview_scaffold/src/dtd/editor_service.dart'; import 'package:widget_preview_scaffold/src/dtd/utils.dart'; /// Provides services, streams, and RPC invocations to interact with Flutter developer tooling. class WidgetPreviewScaffoldDtdServices with DtdEditorService { // WARNING: Keep these constants and services in sync with those defined in the widget preview // scaffold's dtd_services.dart. // // START KEEP SYNCED static const kIsWindows = 'isWindows'; static const kHotRestartPreviewer = 'hotRestartPreviewer'; static const kResolveUri = 'resolveUri'; static const kSetPreference = 'setPreference'; static const kGetPreference = 'getPreference'; static const kGetDevToolsUri = 'getDevToolsUri'; /// Error code for RpcException thrown when attempting to load a key from /// persistent preferences that doesn't have an entry. static const kNoValueForKey = 200; // END KEEP SYNCED /// Connects to the Dart Tooling Daemon (DTD) specified by the Flutter tool. /// /// If the connection is successful, the Widget Preview Scaffold will register services and /// subscribe to various streams to interact directly with other tooling (e.g., IDEs). Future connect({Uri? dtdUri}) async { final Uri dtdWsUri = dtdUri ?? Uri.parse(kWidgetPreviewDtdUri); dtd = await DartToolingDaemon.connect(dtdWsUri); unawaited( dtd.postEvent( kWidgetPreviewScaffoldStream, 'Connected', const {}, ), ); await _determineIfWindows(); await initializeEditorService(this); } /// Disposes the DTD connection. @override Future dispose() async { super.dispose(); await dtd.close(); } Future _call( String methodName, { Map? params, }) => dtd.safeCall(kWidgetPreviewService, methodName, params: params); /// Returns `true` if the operating system is Windows. late final bool isWindows; Future _determineIfWindows() async { isWindows = (BoolResponse.fromDTDResponse( (await _call(kIsWindows))!, )).value!; } /// Trigger a hot restart of the widget preview scaffold. Future hotRestartPreviewer() => _call(kHotRestartPreviewer); /// Resolves a package:// URI to a file:// URI using the package_config. /// /// Returns null if [uri] can not be resolved. Future resolveUri(Uri uri) async { final response = await _call(kResolveUri, params: {'uri': uri.toString()}); if (response == null) { return null; } final result = StringResponse.fromDTDResponse(response).value; return result == null ? null : Uri.parse(result); } /// Retrieves an arbitrary value associated with [key] from the persistent /// preferences map. /// /// Returns null if [key] is not in the map. Future getPreference(String key) async { try { final response = await _call(kGetPreference, params: {'key': key}); return switch (response?.type) { 'StringResponse' => StringResponse.fromDTDResponse(response!).value, 'BoolResponse' => BoolResponse.fromDTDResponse(response!).value, _ => throw StateError('Unexpected response type: ${response?.type}'), }; } on RpcException catch (e) { if (e.code == kNoValueForKey) { return null; } rethrow; } } /// Retrieves the state of flag [key] from the persistent preferences map. /// /// If [key] is not set, [defaultValue] is returned. Future getFlag(String key, {bool defaultValue = false}) async { final result = await getPreference(key) as bool?; return result ?? defaultValue; } /// Sets [key] to [value] in the persistent preferences map. Future setPreference(String key, Object? value) async { await _call(kSetPreference, params: {'key': key, 'value': value}); } /// Retrieves the DevTools URI for the previewer instance. Future getDevToolsUri() async { final result = StringResponse.fromDTDResponse( (await _call(kGetDevToolsUri))!, ); return Uri.parse(result.value!); } @override late final DartToolingDaemon dtd; }