| // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file |
| // for details. 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:dds_service_extensions/dap.dart'; |
| import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; |
| |
| import '../dap.dart'; |
| import 'dap/adapters/dds_hosted_adapter.dart'; |
| import 'dap/constants.dart'; |
| import 'dds_impl.dart'; |
| |
| /// Responds to incoming DAP messages using a debug adapter connected to DDS. |
| class DapHandler { |
| DapHandler(this.dds); |
| |
| final _initializedCompleter = Completer<void>(); |
| |
| Future<Map<String, dynamic>> sendRequest( |
| DdsHostedAdapter adapter, |
| json_rpc.Parameters parameters, |
| ) async { |
| if (adapter.ddsUri == null) { |
| await _startAdapter(adapter); |
| } |
| |
| // TODO(helin24): Consider a sequence offset for incoming messages to avoid |
| // overlapping sequence numbers with startup requests. |
| final message = parameters['message'].asString; |
| |
| // TODO(dantup): If/when DAP needs to care about ordering (eg. it handles |
| // both requests and events), this will need to be changed to have the |
| // caller provide a "responseWriter" function so the result can be written |
| // directly to the stream synchronously, to avoid future events being able |
| // to be inserted before the response (eg. initializedEvent). |
| final responseCompleter = Completer<Response>(); |
| adapter.handleMessage(message, responseCompleter.complete); |
| final result = await responseCompleter.future; |
| |
| return <String, dynamic>{ |
| 'type': 'DapResponse', |
| 'dapResponse': result, |
| }; |
| } |
| |
| void _handleEvent(Event event) { |
| if (event.event == 'initialized') { |
| _initializedCompleter.complete(); |
| } |
| dds.streamManager.streamNotify(DapEventStreams.kDAP, { |
| 'streamId': DapEventStreams.kDAP, |
| 'event': { |
| 'kind': DapEventKind.kDAPEvent, |
| 'timestamp': DateTime.now().millisecondsSinceEpoch, |
| 'dapData': event, |
| }, |
| }); |
| } |
| |
| Future<void> _startAdapter(DdsHostedAdapter adapter) async { |
| adapter.ddsUri = dds.uri; |
| adapter.setEventHandler(_handleEvent); |
| |
| // TODO(helin24): Most likely we'll want the client to do these |
| // initialization steps so that clients can differentiate capabilities. This |
| // may require a custom stream for the debug adapter. |
| |
| // Each DAP request has a `seq` number (essentially a message ID) which |
| // should be unique. |
| // |
| // We send a few requests to initialize the adapter, but these are not |
| // visible to the DDS client so if we start at 1, the IDs will be |
| // reused. |
| // |
| // To avoid that, for our own initialization requests, use negative numbers |
| // (though they must still ascend) so there's no overlay with the messages |
| // we'll forward from the DDS client. |
| int seq = -1000; |
| await adapter.initializeRequest( |
| Request( |
| command: Command.initialize, |
| seq: seq, |
| ), |
| DartInitializeRequestArguments( |
| adapterID: 'dds-dap-handler', |
| ), |
| (capabilities) {}, |
| ); |
| await _initializedCompleter.future; |
| await adapter.configurationDoneRequest( |
| Request( |
| arguments: const {}, |
| command: Command.configurationDone, |
| seq: seq++, |
| ), |
| ConfigurationDoneArguments(), |
| noopCallback, |
| ); |
| await adapter.attachRequest( |
| Request( |
| arguments: const {}, |
| command: Command.attach, |
| seq: seq++, |
| ), |
| DartAttachRequestArguments( |
| vmServiceUri: dds.remoteVmServiceUri.toString(), |
| ), |
| noopCallback, |
| ); |
| // Wait for the debugger to fully initialize, because the request that |
| // triggered this initialization may require things like isolates that will |
| // only be known after the debugger has initialized. |
| await adapter.debuggerInitialized; |
| } |
| |
| final DartDevelopmentServiceImpl dds; |
| } |
| |
| void noopCallback() {} |