mirror of
https://github.com/dart-lang/sdk
synced 2024-10-06 12:57:42 +00:00
Implement the legacy protocol for showing messages and URIs to users
There is a desire to unify the two subclasses of the analysis server, but I didn't attempt to do that in this CL. Change-Id: I17bbf4f6247fc547df1c82f02e74c50eabe7aadc Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/291000 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com> Reviewed-by: Jaime Wren <jwren@google.com>
This commit is contained in:
parent
24d210a18c
commit
f4bbc427c3
|
@ -58,7 +58,7 @@ class Notification {
|
|||
/// A request that was received from the client.
|
||||
///
|
||||
/// Clients may not extend, implement or mix-in this class.
|
||||
class Request {
|
||||
class Request extends RequestOrResponse {
|
||||
/// The name of the JSON attribute containing the id of the request.
|
||||
static const String ID = 'id';
|
||||
|
||||
|
@ -73,6 +73,7 @@ class Request {
|
|||
static const String CLIENT_REQUEST_TIME = 'clientRequestTime';
|
||||
|
||||
/// The unique identifier used to identify this request.
|
||||
@override
|
||||
final String id;
|
||||
|
||||
/// The method being requested.
|
||||
|
@ -271,10 +272,18 @@ abstract class RequestHandler {
|
|||
Response? handleRequest(Request request, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
/// A request or response that was received from the client.
|
||||
///
|
||||
/// Clients may not extend, implement or mix-in this class.
|
||||
abstract class RequestOrResponse {
|
||||
/// The unique identifier associated with this request or response.
|
||||
String get id;
|
||||
}
|
||||
|
||||
/// A response to a request.
|
||||
///
|
||||
/// Clients may not extend, implement or mix-in this class.
|
||||
class Response {
|
||||
class Response extends RequestOrResponse {
|
||||
/// The [Response] instance that is returned when a real [Response] cannot
|
||||
/// be provided at the moment.
|
||||
static final Response DELAYED_RESPONSE = Response('DELAYED_RESPONSE');
|
||||
|
@ -291,6 +300,7 @@ class Response {
|
|||
|
||||
/// The unique identifier used to identify the request that this response is
|
||||
/// associated with.
|
||||
@override
|
||||
final String id;
|
||||
|
||||
/// The error that was caused by attempting to handle the request, or `null`
|
||||
|
@ -540,4 +550,30 @@ class Response {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a response parsed from the given [data], or `null` if the [data] is
|
||||
/// not a valid json representation of a response. The [data] is expected to
|
||||
/// have the following format:
|
||||
///
|
||||
/// {
|
||||
/// 'id': String,
|
||||
/// 'result': {
|
||||
/// parameter_name: value
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// where the result is optional.
|
||||
///
|
||||
/// The result can contain any number of name/value pairs.
|
||||
static Response? fromString(String data) {
|
||||
try {
|
||||
var result = json.decode(data);
|
||||
if (result is Map<String, Object?>) {
|
||||
return Response.fromJson(result);
|
||||
}
|
||||
return null;
|
||||
} catch (exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,7 +86,7 @@ abstract class ByteStreamServerChannel implements ServerCommunicationChannel {
|
|||
bool _closeRequested = false;
|
||||
|
||||
@override
|
||||
late final Stream<Request> requests = _lines.transform(
|
||||
late final Stream<RequestOrResponse> requests = _lines.transform(
|
||||
StreamTransformer.fromHandlers(
|
||||
handleData: _readRequest,
|
||||
handleDone: (sink) {
|
||||
|
@ -135,6 +135,18 @@ abstract class ByteStreamServerChannel implements ServerCommunicationChannel {
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void sendRequest(Request request) {
|
||||
// Don't send any further requests after the communication channel is
|
||||
// closed.
|
||||
if (_closeRequested) {
|
||||
return;
|
||||
}
|
||||
var jsonEncoding = json.encode(request.toJson());
|
||||
_outputLine(jsonEncoding);
|
||||
_instrumentationService.logRequest(jsonEncoding);
|
||||
}
|
||||
|
||||
@override
|
||||
void sendResponse(Response response) {
|
||||
// Don't send any further responses after the communication channel is
|
||||
|
@ -159,21 +171,26 @@ abstract class ByteStreamServerChannel implements ServerCommunicationChannel {
|
|||
|
||||
/// Read a request from the given [data] and use the given function to handle
|
||||
/// the request.
|
||||
void _readRequest(String data, Sink<Request> sink) {
|
||||
void _readRequest(String data, Sink<RequestOrResponse> sink) {
|
||||
// Ignore any further requests after the communication channel is closed.
|
||||
if (_closed.isCompleted) {
|
||||
return;
|
||||
}
|
||||
_instrumentationService.logRequest(data);
|
||||
// Parse the string as a JSON descriptor and process the resulting
|
||||
// structure as a request.
|
||||
var request = Request.fromString(data);
|
||||
if (request == null) {
|
||||
// structure as either a request or a response.
|
||||
var requestOrResponse =
|
||||
Request.fromString(data) ?? Response.fromString(data);
|
||||
if (requestOrResponse == null) {
|
||||
// If the data isn't valid, then assume it was an invalid request so that
|
||||
// clients won't be left waiting for a response.
|
||||
sendResponse(Response.invalidRequestFormat());
|
||||
return;
|
||||
}
|
||||
_requestStatistics?.addRequest(request);
|
||||
sink.add(request);
|
||||
if (requestOrResponse is Request) {
|
||||
_requestStatistics?.addRequest(requestOrResponse);
|
||||
}
|
||||
sink.add(requestOrResponse);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -99,8 +99,8 @@ class ResponseConverter extends Converter<Map<String, Object?>, Response?> {
|
|||
/// objects that allow an [AnalysisServer] to receive [Request]s and to return
|
||||
/// both [Response]s and [Notification]s.
|
||||
abstract class ServerCommunicationChannel {
|
||||
/// The single-subscription stream of requests.
|
||||
Stream<Request> get requests;
|
||||
/// The single-subscription stream of requests and responses.
|
||||
Stream<RequestOrResponse> get requests;
|
||||
|
||||
/// Close the communication channel.
|
||||
void close();
|
||||
|
@ -108,6 +108,9 @@ abstract class ServerCommunicationChannel {
|
|||
/// Send the given [notification] to the client.
|
||||
void sendNotification(Notification notification);
|
||||
|
||||
/// Send the given [request] to the client.
|
||||
void sendRequest(Request request);
|
||||
|
||||
/// Send the given [response] to the client.
|
||||
void sendResponse(Response response);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
// 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:analysis_server/protocol/protocol.dart';
|
||||
import 'package:analysis_server/protocol/protocol_generated.dart';
|
||||
import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
|
||||
|
||||
/// The handler for the `server.setClientCapabilities` request.
|
||||
class ServerSetClientCapabilitiesHandler extends LegacyHandler {
|
||||
/// Initialize a newly created handler to be able to service requests for the
|
||||
/// [server].
|
||||
ServerSetClientCapabilitiesHandler(
|
||||
super.server, super.request, super.cancellationToken, super.performance);
|
||||
|
||||
@override
|
||||
Future<void> handle() async {
|
||||
try {
|
||||
server.clientCapabilities =
|
||||
ServerSetClientCapabilitiesParams.fromRequest(request);
|
||||
} on RequestFailure catch (exception) {
|
||||
sendResponse(exception.response);
|
||||
return;
|
||||
}
|
||||
sendResult(ServerSetClientCapabilitiesResult());
|
||||
}
|
||||
}
|
|
@ -72,6 +72,7 @@ import 'package:analysis_server/src/handler/legacy/search_get_element_declaratio
|
|||
import 'package:analysis_server/src/handler/legacy/search_get_type_hierarchy.dart';
|
||||
import 'package:analysis_server/src/handler/legacy/server_cancel_request.dart';
|
||||
import 'package:analysis_server/src/handler/legacy/server_get_version.dart';
|
||||
import 'package:analysis_server/src/handler/legacy/server_set_client_capabilities.dart';
|
||||
import 'package:analysis_server/src/handler/legacy/server_set_subscriptions.dart';
|
||||
import 'package:analysis_server/src/handler/legacy/server_shutdown.dart';
|
||||
import 'package:analysis_server/src/handler/legacy/unsupported_request.dart';
|
||||
|
@ -170,7 +171,7 @@ class AnalysisServerOptions {
|
|||
class LegacyAnalysisServer extends AnalysisServer {
|
||||
/// A map from the name of a request to a function used to create a request
|
||||
/// handler.
|
||||
static final Map<String, HandlerGenerator> handlerGenerators = {
|
||||
static final Map<String, HandlerGenerator> requestHandlerGenerators = {
|
||||
ANALYSIS_REQUEST_GET_ERRORS: AnalysisGetErrorsHandler.new,
|
||||
ANALYSIS_REQUEST_GET_HOVER: AnalysisGetHoverHandler.new,
|
||||
ANALYSIS_REQUEST_GET_IMPORTED_ELEMENTS:
|
||||
|
@ -247,10 +248,12 @@ class LegacyAnalysisServer extends AnalysisServer {
|
|||
SearchGetElementDeclarationsHandler.new,
|
||||
SEARCH_REQUEST_GET_TYPE_HIERARCHY: SearchGetTypeHierarchyHandler.new,
|
||||
//
|
||||
SERVER_REQUEST_CANCEL_REQUEST: ServerCancelRequestHandler.new,
|
||||
SERVER_REQUEST_GET_VERSION: ServerGetVersionHandler.new,
|
||||
SERVER_REQUEST_SET_CLIENT_CAPABILITIES:
|
||||
ServerSetClientCapabilitiesHandler.new,
|
||||
SERVER_REQUEST_SET_SUBSCRIPTIONS: ServerSetSubscriptionsHandler.new,
|
||||
SERVER_REQUEST_SHUTDOWN: ServerShutdownHandler.new,
|
||||
SERVER_REQUEST_CANCEL_REQUEST: ServerCancelRequestHandler.new,
|
||||
};
|
||||
|
||||
/// The channel from which requests are received and to which responses should
|
||||
|
@ -278,6 +281,11 @@ class LegacyAnalysisServer extends AnalysisServer {
|
|||
/// notifications should be sent.
|
||||
Map<AnalysisService, Set<String>> analysisServices = {};
|
||||
|
||||
/// The most recently registered set of client capabilities. The default is to
|
||||
/// have no registered requests.
|
||||
ServerSetClientCapabilitiesParams clientCapabilities =
|
||||
ServerSetClientCapabilitiesParams([]);
|
||||
|
||||
/// A table mapping [FlutterService]s to the file paths for which these
|
||||
/// notifications should be sent.
|
||||
Map<FlutterService, Set<String>> flutterServices = {};
|
||||
|
@ -328,6 +336,14 @@ class LegacyAnalysisServer extends AnalysisServer {
|
|||
final StreamController<Request> discardedRequests =
|
||||
StreamController.broadcast(sync: true);
|
||||
|
||||
/// The index of the next request from the server to the client.
|
||||
int nextServerRequestId = 0;
|
||||
|
||||
/// A table mapping the ids of requests sent from the server to the client
|
||||
/// that have not yet received a response, to the completer used to return the
|
||||
/// response when it has been received.
|
||||
Map<String, Completer<Response>> pendingServerRequests = {};
|
||||
|
||||
/// Initialize a newly created server to receive requests from and send
|
||||
/// responses to the given [channel].
|
||||
///
|
||||
|
@ -383,7 +399,7 @@ class LegacyAnalysisServer extends AnalysisServer {
|
|||
).toNotification(),
|
||||
);
|
||||
debounceRequests(channel, discardedRequests)
|
||||
.listen(handleRequest, onDone: done, onError: error);
|
||||
.listen(handleRequestOrResponse, onDone: done, onError: error);
|
||||
refactoringWorkspace = RefactoringWorkspace(driverMap.values, searchEngine);
|
||||
_newRefactoringManager();
|
||||
}
|
||||
|
@ -426,6 +442,12 @@ class LegacyAnalysisServer extends AnalysisServer {
|
|||
return sdkManager.defaultSdkDirectory;
|
||||
}
|
||||
|
||||
bool get supportsOpenUriNotification =>
|
||||
clientCapabilities.requests.contains('openUrlRequest');
|
||||
|
||||
bool get supportsShowMessageRequest =>
|
||||
clientCapabilities.requests.contains('showMessageRequest');
|
||||
|
||||
void cancelRequest(String id) {
|
||||
cancellationTokens[id]?.cancel();
|
||||
}
|
||||
|
@ -473,7 +495,7 @@ class LegacyAnalysisServer extends AnalysisServer {
|
|||
|
||||
var cancellationToken = CancelableToken();
|
||||
cancellationTokens[request.id] = cancellationToken;
|
||||
var generator = handlerGenerators[request.method];
|
||||
var generator = requestHandlerGenerators[request.method];
|
||||
if (generator != null) {
|
||||
var handler =
|
||||
generator(this, request, cancellationToken, performance);
|
||||
|
@ -513,6 +535,23 @@ class LegacyAnalysisServer extends AnalysisServer {
|
|||
});
|
||||
}
|
||||
|
||||
/// Handle a [request] that was read from the communication channel.
|
||||
void handleRequestOrResponse(RequestOrResponse requestOrResponse) {
|
||||
if (requestOrResponse is Request) {
|
||||
handleRequest(requestOrResponse);
|
||||
} else if (requestOrResponse is Response) {
|
||||
handleResponse(requestOrResponse);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a [response] that was read from the communication channel.
|
||||
void handleResponse(Response response) {
|
||||
var completer = pendingServerRequests.remove(response.id);
|
||||
if (completer != null) {
|
||||
completer.complete(response);
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if the [path] is both absolute and normalized.
|
||||
bool isAbsoluteAndNormalized(String path) {
|
||||
var pathContext = resourceProvider.pathContext;
|
||||
|
@ -542,6 +581,22 @@ class LegacyAnalysisServer extends AnalysisServer {
|
|||
channel.sendNotification(notification);
|
||||
}
|
||||
|
||||
Future<void> sendOpenUriNotification(Uri uri) {
|
||||
assert(supportsOpenUriNotification);
|
||||
var requestId = (nextServerRequestId++).toString();
|
||||
var request =
|
||||
ServerOpenUrlRequestParams(uri.toString()).toRequest(requestId);
|
||||
return sendRequest(request);
|
||||
}
|
||||
|
||||
/// Send the given [request] to the client.
|
||||
Future<Response> sendRequest(Request request) {
|
||||
var completer = Completer<Response>();
|
||||
pendingServerRequests[request.id] = completer;
|
||||
channel.sendRequest(request);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
/// Send the given [response] to the client.
|
||||
void sendResponse(Response response) {
|
||||
channel.sendResponse(response);
|
||||
|
@ -695,6 +750,20 @@ class LegacyAnalysisServer extends AnalysisServer {
|
|||
}
|
||||
}
|
||||
|
||||
Future<String?> showUserPrompt(
|
||||
MessageType type,
|
||||
String message,
|
||||
List<String> actionLabels,
|
||||
) async {
|
||||
assert(supportsShowMessageRequest);
|
||||
var requestId = (nextServerRequestId++).toString();
|
||||
var actions = actionLabels.map((label) => MessageAction(label)).toList();
|
||||
var request = ServerShowMessageRequestParams(type, message, actions)
|
||||
.toRequest(requestId);
|
||||
final response = await sendRequest(request);
|
||||
return response.result?['action'] as String?;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> shutdown() async {
|
||||
await super.shutdown();
|
||||
|
|
|
@ -18,26 +18,26 @@ import 'package:analysis_server/src/channel/channel.dart';
|
|||
/// empty response is enough.
|
||||
///
|
||||
/// Discarded requests are reported into [discardedRequests].
|
||||
Stream<Request> debounceRequests(
|
||||
Stream<RequestOrResponse> debounceRequests(
|
||||
ServerCommunicationChannel channel,
|
||||
StreamController<Request> discardedRequests,
|
||||
StreamController<RequestOrResponse> discardedRequests,
|
||||
) {
|
||||
return _DebounceRequests(channel, discardedRequests).requests;
|
||||
}
|
||||
|
||||
class _DebounceRequests {
|
||||
final ServerCommunicationChannel channel;
|
||||
final StreamController<Request> discardedRequests;
|
||||
late final Stream<Request> requests;
|
||||
final StreamController<RequestOrResponse> discardedRequests;
|
||||
late final Stream<RequestOrResponse> requests;
|
||||
|
||||
_DebounceRequests(this.channel, this.discardedRequests) {
|
||||
var buffer = <Request>[];
|
||||
var buffer = <RequestOrResponse>[];
|
||||
Timer? timer;
|
||||
|
||||
requests = channel.requests.transform(
|
||||
StreamTransformer.fromHandlers(
|
||||
handleData: (request, sink) {
|
||||
buffer.add(request);
|
||||
handleData: (requestOrResponse, sink) {
|
||||
buffer.add(requestOrResponse);
|
||||
// Accumulate requests for a short period of time.
|
||||
// When we were busy processing a request, the client could put
|
||||
// multiple requests into the event queue. So, when we look, we will
|
||||
|
@ -55,28 +55,31 @@ class _DebounceRequests {
|
|||
);
|
||||
}
|
||||
|
||||
List<Request> _filterCompletion(List<Request> requests) {
|
||||
var reversed = <Request>[];
|
||||
List<RequestOrResponse> _filterCompletion(List<RequestOrResponse> requests) {
|
||||
var reversed = <RequestOrResponse>[];
|
||||
var abortCompletionRequests = false;
|
||||
for (var request in requests.reversed) {
|
||||
if (request.method == ANALYSIS_REQUEST_UPDATE_CONTENT) {
|
||||
abortCompletionRequests = true;
|
||||
}
|
||||
if (request.method == COMPLETION_REQUEST_GET_SUGGESTIONS2) {
|
||||
if (abortCompletionRequests) {
|
||||
discardedRequests.add(request);
|
||||
var params = CompletionGetSuggestions2Params.fromRequest(request);
|
||||
var offset = params.offset;
|
||||
channel.sendResponse(
|
||||
CompletionGetSuggestions2Result(offset, 0, [], true)
|
||||
.toResponse(request.id),
|
||||
);
|
||||
continue;
|
||||
} else {
|
||||
for (var requestOrResponse in requests.reversed) {
|
||||
if (requestOrResponse is Request) {
|
||||
if (requestOrResponse.method == ANALYSIS_REQUEST_UPDATE_CONTENT) {
|
||||
abortCompletionRequests = true;
|
||||
}
|
||||
if (requestOrResponse.method == COMPLETION_REQUEST_GET_SUGGESTIONS2) {
|
||||
if (abortCompletionRequests) {
|
||||
discardedRequests.add(requestOrResponse);
|
||||
var params =
|
||||
CompletionGetSuggestions2Params.fromRequest(requestOrResponse);
|
||||
var offset = params.offset;
|
||||
channel.sendResponse(
|
||||
CompletionGetSuggestions2Result(offset, 0, [], true)
|
||||
.toResponse(requestOrResponse.id),
|
||||
);
|
||||
continue;
|
||||
} else {
|
||||
abortCompletionRequests = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
reversed.add(request);
|
||||
reversed.add(requestOrResponse);
|
||||
}
|
||||
return reversed.reversed.toList();
|
||||
}
|
||||
|
|
|
@ -129,15 +129,15 @@ class DevAnalysisServer {
|
|||
}
|
||||
});
|
||||
|
||||
await _channel
|
||||
.sendRequest(Request('${_nextId++}', 'server.setSubscriptions', {
|
||||
await _channel.simulateRequestFromClient(
|
||||
Request('${_nextId++}', 'server.setSubscriptions', {
|
||||
'subscriptions': ['STATUS'],
|
||||
}));
|
||||
|
||||
directories =
|
||||
directories.map((dir) => path.normalize(path.absolute(dir))).toList();
|
||||
|
||||
await _channel.sendRequest(Request(
|
||||
await _channel.simulateRequestFromClient(Request(
|
||||
'${_nextId++}',
|
||||
'analysis.setAnalysisRoots',
|
||||
{'included': directories, 'excluded': []},
|
||||
|
@ -146,7 +146,7 @@ class DevAnalysisServer {
|
|||
return whenComplete.future.whenComplete(() {
|
||||
notificationSubscriptions.cancel();
|
||||
|
||||
_channel.sendRequest(Request(
|
||||
_channel.simulateRequestFromClient(Request(
|
||||
'${_nextId++}',
|
||||
'analysis.setAnalysisRoots',
|
||||
{'included': [], 'excluded': []},
|
||||
|
@ -156,7 +156,7 @@ class DevAnalysisServer {
|
|||
}
|
||||
|
||||
class DevChannel implements ServerCommunicationChannel {
|
||||
final StreamController<Request> _requestController =
|
||||
final StreamController<RequestOrResponse> _requestController =
|
||||
StreamController.broadcast();
|
||||
|
||||
final StreamController<Notification> _notificationController =
|
||||
|
@ -167,7 +167,7 @@ class DevChannel implements ServerCommunicationChannel {
|
|||
Stream<Notification> get onNotification => _notificationController.stream;
|
||||
|
||||
@override
|
||||
Stream<Request> get requests => _requestController.stream;
|
||||
Stream<RequestOrResponse> get requests => _requestController.stream;
|
||||
|
||||
@override
|
||||
void close() {
|
||||
|
@ -179,11 +179,10 @@ class DevChannel implements ServerCommunicationChannel {
|
|||
_notificationController.add(notification);
|
||||
}
|
||||
|
||||
Future<Response> sendRequest(Request request) {
|
||||
var completer = Completer<Response>();
|
||||
_responseCompleters[request.id] = completer;
|
||||
_requestController.add(request);
|
||||
return completer.future;
|
||||
@override
|
||||
void sendRequest(Request request) {
|
||||
throw UnimplementedError(
|
||||
'sendRequest (did you mean simulateRequestFromClient?)');
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -191,4 +190,11 @@ class DevChannel implements ServerCommunicationChannel {
|
|||
var completer = _responseCompleters.remove(response.id);
|
||||
completer?.complete(response);
|
||||
}
|
||||
|
||||
Future<Response> simulateRequestFromClient(Request request) {
|
||||
var completer = Completer<Response>();
|
||||
_responseCompleters[request.id] = completer;
|
||||
_requestController.add(request);
|
||||
return completer.future;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,8 +73,9 @@ class SocketServer implements AbstractSocketServer {
|
|||
var error = RequestError(
|
||||
RequestErrorCode.SERVER_ALREADY_STARTED, 'Server already started');
|
||||
serverChannel.sendResponse(Response('', error: error));
|
||||
serverChannel.requests.listen((Request request) {
|
||||
serverChannel.sendResponse(Response(request.id, error: error));
|
||||
serverChannel.requests.listen((RequestOrResponse requestOrResponse) {
|
||||
serverChannel
|
||||
.sendResponse(Response(requestOrResponse.id, error: error));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -19,7 +19,8 @@ import 'package:watcher/watcher.dart';
|
|||
|
||||
/// A mock [ServerCommunicationChannel] for testing [AnalysisServer].
|
||||
class MockServerChannel implements ServerCommunicationChannel {
|
||||
StreamController<Request> requestController = StreamController<Request>();
|
||||
StreamController<RequestOrResponse> requestController =
|
||||
StreamController<RequestOrResponse>();
|
||||
StreamController<Response> responseController =
|
||||
StreamController<Response>.broadcast();
|
||||
StreamController<Notification> notificationController =
|
||||
|
@ -28,6 +29,7 @@ class MockServerChannel implements ServerCommunicationChannel {
|
|||
|
||||
List<Response> responsesReceived = [];
|
||||
List<Notification> notificationsReceived = [];
|
||||
List<Request> serverRequestsSent = [];
|
||||
|
||||
bool _closed = false;
|
||||
|
||||
|
@ -41,7 +43,7 @@ class MockServerChannel implements ServerCommunicationChannel {
|
|||
}
|
||||
|
||||
@override
|
||||
Stream<Request> get requests => requestController.stream;
|
||||
Stream<RequestOrResponse> get requests => requestController.stream;
|
||||
|
||||
@override
|
||||
void close() {
|
||||
|
@ -73,21 +75,9 @@ class MockServerChannel implements ServerCommunicationChannel {
|
|||
notificationController.add(notification);
|
||||
}
|
||||
|
||||
/// Send the given [request] to the server and return a future that will
|
||||
/// complete when a response associated with the [request] has been received.
|
||||
/// The value of the future will be the received response. If [throwOnError] is
|
||||
/// `true` (the default) then the returned future will throw an exception if a
|
||||
/// server error is reported before the response has been received.
|
||||
Future<Response> sendRequest(Request request, {bool throwOnError = true}) {
|
||||
// TODO(brianwilkerson) Attempt to remove the `throwOnError` parameter and
|
||||
// have the default behavior be the only behavior.
|
||||
// No further requests should be sent after the connection is closed.
|
||||
if (_closed) {
|
||||
throw Exception('sendRequest after connection closed');
|
||||
}
|
||||
// Wrap send request in future to simulate WebSocket.
|
||||
Future(() => requestController.add(request));
|
||||
return waitForResponse(request, throwOnError: throwOnError);
|
||||
@override
|
||||
void sendRequest(Request request) {
|
||||
serverRequestsSent.add(request);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -101,14 +91,45 @@ class MockServerChannel implements ServerCommunicationChannel {
|
|||
Future(() => responseController.add(response));
|
||||
}
|
||||
|
||||
/// Send the given [request] to the server as if it had been sent from the
|
||||
/// client, and return a future that will complete when a response associated
|
||||
/// with the [request] has been received.
|
||||
///
|
||||
/// The value of the future will be the received response. If [throwOnError]
|
||||
/// is `true` (the default) then the returned future will throw an exception
|
||||
/// if a server error is reported before the response has been received.
|
||||
Future<Response> simulateRequestFromClient(Request request,
|
||||
{bool throwOnError = true}) {
|
||||
// TODO(brianwilkerson) Attempt to remove the `throwOnError` parameter and
|
||||
// have the default behavior be the only behavior.
|
||||
// No further requests should be sent after the connection is closed.
|
||||
if (_closed) {
|
||||
throw Exception('simulateRequestFromClient after connection closed');
|
||||
}
|
||||
// Wrap send request in future to simulate WebSocket.
|
||||
Future(() => requestController.add(request));
|
||||
return waitForResponse(request, throwOnError: throwOnError);
|
||||
}
|
||||
|
||||
/// Send the given [response] to the server as if it had been sent from the
|
||||
/// client.
|
||||
Future<void> simulateResponseFromClient(Response response) {
|
||||
// No further requests should be sent after the connection is closed.
|
||||
if (_closed) {
|
||||
throw Exception('simulateRequestFromClient after connection closed');
|
||||
}
|
||||
// Wrap send request in future to simulate WebSocket.
|
||||
return Future(() => requestController.add(response));
|
||||
}
|
||||
|
||||
/// Return a future that will complete when a response associated with the
|
||||
/// given [request] has been received. The value of the future will be the
|
||||
/// received response. If [throwOnError] is `true` (the default) then the
|
||||
/// returned future will throw an exception if a server error is reported
|
||||
/// before the response has been received.
|
||||
///
|
||||
/// Unlike [sendRequest], this method assumes that the [request] has already
|
||||
/// been sent to the server.
|
||||
/// Unlike [simulateRequestFromClient], this method assumes that the [request]
|
||||
/// has already been sent to the server.
|
||||
Future<Response> waitForResponse(Request request,
|
||||
{bool throwOnError = true}) {
|
||||
// TODO(brianwilkerson) Attempt to remove the `throwOnError` parameter and
|
||||
|
|
|
@ -64,7 +64,7 @@ class A {}
|
|||
await setRoots(included: [], excluded: []);
|
||||
|
||||
var request = _createGetErrorsRequest(testFile.path);
|
||||
var response = await serverChannel.sendRequest(request);
|
||||
var response = await serverChannel.simulateRequestFromClient(request);
|
||||
assertResponseFailure(
|
||||
response,
|
||||
requestId: _requestId,
|
||||
|
|
|
@ -84,7 +84,7 @@ String f() {
|
|||
/// {@tool dartpad}
|
||||
/// ** See code in $examplePath **
|
||||
/// {@end-tool}
|
||||
///
|
||||
///
|
||||
/// {@tool dartpad}
|
||||
/// ** See code in $example2Path **
|
||||
/// {@end-tool}
|
||||
|
@ -158,7 +158,7 @@ class Bar {
|
|||
Future<void> test_fileDoesNotExist() async {
|
||||
var file = convertPath('$testPackageLibPath/doesNotExist.dart');
|
||||
var request = _createGetNavigationRequest(file, 0, 100);
|
||||
var response = await serverChannel.sendRequest(request);
|
||||
var response = await serverChannel.simulateRequestFromClient(request);
|
||||
expect(response.error, isNull);
|
||||
var result = response.result!;
|
||||
expect(result['files'], isEmpty);
|
||||
|
@ -442,7 +442,7 @@ void f() {
|
|||
}
|
||||
|
||||
var request = _createGetNavigationRequest(file.path, offset, length);
|
||||
var response = await serverChannel.sendRequest(request);
|
||||
var response = await serverChannel.simulateRequestFromClient(request);
|
||||
var result = AnalysisGetNavigationResult.fromResponse(response);
|
||||
targetFiles = result.files;
|
||||
targets = result.targets;
|
||||
|
|
|
@ -125,7 +125,7 @@ class ContextResolutionTest with ResourceProviderMixin {
|
|||
void createDefaultFiles() {}
|
||||
|
||||
Future<Response> handleRequest(Request request) async {
|
||||
return await serverChannel.sendRequest(request);
|
||||
return await serverChannel.simulateRequestFromClient(request);
|
||||
}
|
||||
|
||||
/// Validates that the given [request] is handled successfully.
|
||||
|
|
|
@ -322,7 +322,7 @@ analyzer:
|
|||
|
||||
Future<void> test_shutdown() {
|
||||
var request = Request('my28', SERVER_REQUEST_SHUTDOWN);
|
||||
return channel.sendRequest(request).then((Response response) {
|
||||
return channel.simulateRequestFromClient(request).then((Response response) {
|
||||
expect(response.id, equals('my28'));
|
||||
expect(response.error, isNull);
|
||||
});
|
||||
|
@ -330,7 +330,7 @@ analyzer:
|
|||
|
||||
Future<void> test_unknownRequest() {
|
||||
var request = Request('my22', 'randomRequest');
|
||||
return channel.sendRequest(request).then((Response response) {
|
||||
return channel.simulateRequestFromClient(request).then((Response response) {
|
||||
expect(response.id, equals('my22'));
|
||||
expect(response.error, isNotNull);
|
||||
});
|
||||
|
|
|
@ -111,7 +111,7 @@ class ByteStreamServerChannelTest {
|
|||
late Stream<String> outputLineStream;
|
||||
|
||||
/// Stream of requests received from the channel via [listen()].
|
||||
late Stream<Request> requestStream;
|
||||
late Stream<RequestOrResponse> requestStream;
|
||||
|
||||
/// Stream of errors received from the channel via [listen()].
|
||||
late Stream<Object?> errorStream;
|
||||
|
@ -129,14 +129,14 @@ class ByteStreamServerChannelTest {
|
|||
var outputSink = IOSink(outputStream);
|
||||
channel = InputOutputByteStreamServerChannel(
|
||||
inputStream.stream, outputSink, InstrumentationService.NULL_SERVICE);
|
||||
var requestStreamController = StreamController<Request>();
|
||||
var requestStreamController = StreamController<RequestOrResponse>();
|
||||
requestStream = requestStreamController.stream;
|
||||
var errorStreamController = StreamController<Object?>();
|
||||
errorStream = errorStreamController.stream;
|
||||
var doneCompleter = Completer();
|
||||
doneFuture = doneCompleter.future;
|
||||
channel.requests.listen((Request request) {
|
||||
requestStreamController.add(request);
|
||||
channel.requests.listen((RequestOrResponse requestOrResponse) {
|
||||
requestStreamController.add(requestOrResponse);
|
||||
}, onError: (error) {
|
||||
errorStreamController.add(error);
|
||||
}, onDone: () {
|
||||
|
@ -164,7 +164,7 @@ class ByteStreamServerChannelTest {
|
|||
}
|
||||
|
||||
Future<void> test_listen_invalidRequest() {
|
||||
inputSink.writeln('{"id":"0"}');
|
||||
inputSink.writeln('{"garbage":"true"}');
|
||||
return inputSink
|
||||
.flush()
|
||||
.then((_) => outputLineStream.first.timeout(Duration(seconds: 1)))
|
||||
|
@ -198,9 +198,12 @@ class ByteStreamServerChannelTest {
|
|||
return inputSink
|
||||
.flush()
|
||||
.then((_) => requestStream.first.timeout(Duration(seconds: 1)))
|
||||
.then((Request request) {
|
||||
expect(request.id, equals('0'));
|
||||
expect(request.method, equals('server.version'));
|
||||
.then((RequestOrResponse requestOrResponse) {
|
||||
if (requestOrResponse is! Request) {
|
||||
fail('Expected a Request');
|
||||
}
|
||||
expect(requestOrResponse.id, equals('0'));
|
||||
expect(requestOrResponse.method, equals('server.version'));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -2361,13 +2361,13 @@ extension MyClassExtension on MyClass {
|
|||
var request1 =
|
||||
CompletionGetSuggestionsParams(testFile.path, completionOffset)
|
||||
.toRequest('7');
|
||||
var responseFuture1 = serverChannel.sendRequest(request1);
|
||||
var responseFuture1 = serverChannel.simulateRequestFromClient(request1);
|
||||
|
||||
// Make another request before the first request completes
|
||||
var request2 =
|
||||
CompletionGetSuggestionsParams(testFile.path, completionOffset)
|
||||
.toRequest('8');
|
||||
var responseFuture2 = serverChannel.sendRequest(request2);
|
||||
var responseFuture2 = serverChannel.simulateRequestFromClient(request2);
|
||||
|
||||
// Await first response
|
||||
var response1 = await responseFuture1;
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// 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:analysis_server/protocol/protocol.dart';
|
||||
import 'package:analysis_server/protocol/protocol_constants.dart';
|
||||
import 'package:analysis_server/protocol/protocol_generated.dart';
|
||||
|
@ -31,6 +33,52 @@ class ServerDomainTest extends PubPackageAnalysisServerTest {
|
|||
}));
|
||||
}
|
||||
|
||||
Future<void> test_openUrl() async {
|
||||
server.clientCapabilities.requests = ['openUrlRequest'];
|
||||
|
||||
// Send the request.
|
||||
var responseFuture =
|
||||
server.sendOpenUriNotification(toUri('https://dart.dev'));
|
||||
expect(serverChannel.serverRequestsSent, hasLength(1));
|
||||
|
||||
// Simulate the response.
|
||||
var request = serverChannel.serverRequestsSent[0];
|
||||
await serverChannel.simulateResponseFromClient(
|
||||
ServerOpenUrlRequestResult().toResponse(request.id));
|
||||
await responseFuture;
|
||||
}
|
||||
|
||||
Future<void> test_setClientCapabilities() async {
|
||||
var requestId = -1;
|
||||
|
||||
Future<void> setCapabilities(
|
||||
{required bool openUrlRequest,
|
||||
required bool showMessageRequest}) async {
|
||||
var requests = [
|
||||
if (openUrlRequest) 'openUrlRequest',
|
||||
if (showMessageRequest) 'showMessageRequest',
|
||||
];
|
||||
if (requestId >= 0) {
|
||||
// This is a bit of a kludge, but the first time this function is called
|
||||
// we won't set the request, we'll just test the default state.
|
||||
var request = ServerSetClientCapabilitiesParams(requests)
|
||||
.toRequest(requestId.toString());
|
||||
await handleSuccessfulRequest(request);
|
||||
}
|
||||
requestId++;
|
||||
|
||||
expect(server.clientCapabilities.requests, requests);
|
||||
expect(server.supportsOpenUriNotification, openUrlRequest);
|
||||
expect(server.supportsShowMessageRequest, showMessageRequest);
|
||||
}
|
||||
|
||||
await setCapabilities(openUrlRequest: false, showMessageRequest: false);
|
||||
await setCapabilities(openUrlRequest: true, showMessageRequest: false);
|
||||
await setCapabilities(openUrlRequest: true, showMessageRequest: true);
|
||||
await setCapabilities(openUrlRequest: false, showMessageRequest: true);
|
||||
await setCapabilities(openUrlRequest: false, showMessageRequest: false);
|
||||
}
|
||||
|
||||
Future<void> test_setSubscriptions_invalidServiceName() async {
|
||||
var request = Request('0', SERVER_REQUEST_SET_SUBSCRIPTIONS, {
|
||||
SUBSCRIPTIONS: ['noSuchService']
|
||||
|
@ -49,6 +97,22 @@ class ServerDomainTest extends PubPackageAnalysisServerTest {
|
|||
expect(server.serverServices, contains(ServerService.STATUS));
|
||||
}
|
||||
|
||||
Future<void> test_showMessage() async {
|
||||
server.clientCapabilities.requests = ['showMessageRequest'];
|
||||
|
||||
// Send the request.
|
||||
var responseFuture =
|
||||
server.showUserPrompt(MessageType.WARNING, 'message', ['a', 'b']);
|
||||
expect(serverChannel.serverRequestsSent, hasLength(1));
|
||||
|
||||
// Simulate the response.
|
||||
var request = serverChannel.serverRequestsSent[0];
|
||||
await serverChannel.simulateResponseFromClient(
|
||||
ServerShowMessageRequestResult('a').toResponse(request.id));
|
||||
var response = await responseFuture;
|
||||
expect(response, 'a');
|
||||
}
|
||||
|
||||
Future<void> test_shutdown() async {
|
||||
var request = ServerShutdownParams().toRequest('0');
|
||||
await handleSuccessfulRequest(request);
|
||||
|
|
|
@ -121,7 +121,7 @@ void f(A a, B b, C c, D d) {
|
|||
0,
|
||||
false)
|
||||
.toRequest('0');
|
||||
return serverChannel.sendRequest(request);
|
||||
return serverChannel.simulateRequestFromClient(request);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -233,7 +233,7 @@ void f(A a, B b, C c, D d) {
|
|||
0,
|
||||
false)
|
||||
.toRequest('0');
|
||||
return serverChannel.sendRequest(request);
|
||||
return serverChannel.simulateRequestFromClient(request);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -879,7 +879,7 @@ class GetAvailableRefactoringsTest extends PubPackageAnalysisServerTest {
|
|||
var request =
|
||||
EditGetAvailableRefactoringsParams(testFile.path, offset, length)
|
||||
.toRequest('0');
|
||||
var response = await serverChannel.sendRequest(request);
|
||||
var response = await serverChannel.simulateRequestFromClient(request);
|
||||
var result = EditGetAvailableRefactoringsResult.fromResponse(response);
|
||||
kinds = result.kinds;
|
||||
}
|
||||
|
@ -1191,7 +1191,7 @@ void f() {
|
|||
0,
|
||||
false)
|
||||
.toRequest('0');
|
||||
return serverChannel.sendRequest(request);
|
||||
return serverChannel.simulateRequestFromClient(request);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1313,7 +1313,7 @@ void f() {
|
|||
testFile.path, findOffset(search), 0, false,
|
||||
options: options)
|
||||
.toRequest('0');
|
||||
return serverChannel.sendRequest(request);
|
||||
return serverChannel.simulateRequestFromClient(request);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1369,7 +1369,7 @@ import 'new_folder/file.dart';
|
|||
// 0 is the id from _sendMoveRequest
|
||||
// 1 is another arbitrary id for the cancel request
|
||||
var request = ServerCancelRequestParams('0').toRequest('1');
|
||||
return serverChannel.sendRequest(request);
|
||||
return serverChannel.simulateRequestFromClient(request);
|
||||
}
|
||||
|
||||
Future<Response> _sendAndCancelMoveRequest(String item) async {
|
||||
|
@ -1385,7 +1385,7 @@ import 'new_folder/file.dart';
|
|||
RefactoringKind.MOVE_FILE, item, 0, 0, false,
|
||||
options: options)
|
||||
.toRequest('0');
|
||||
return serverChannel.sendRequest(request);
|
||||
return serverChannel.simulateRequestFromClient(request);
|
||||
}
|
||||
|
||||
void _setOptions(String newFile) {
|
||||
|
@ -1402,7 +1402,7 @@ class RenameTest extends _AbstractGetRefactoring_Test {
|
|||
testFile.path, findOffset(search), 0, validateOnly,
|
||||
options: options)
|
||||
.toRequest(id);
|
||||
return serverChannel.sendRequest(request);
|
||||
return serverChannel.simulateRequestFromClient(request);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -2589,7 +2589,7 @@ class _AbstractGetRefactoring_Test extends PubPackageAnalysisServerTest {
|
|||
kind, testFile.path, offset, length, validateOnly,
|
||||
options: options)
|
||||
.toRequest('0');
|
||||
return serverChannel.sendRequest(request);
|
||||
return serverChannel.simulateRequestFromClient(request);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -1017,7 +1017,7 @@ class D extends C {}
|
|||
convertPath('/does/not/exist.dart'), 0,
|
||||
superOnly: true)
|
||||
.toRequest(requestId);
|
||||
var response = await serverChannel.sendRequest(request);
|
||||
var response = await serverChannel.simulateRequestFromClient(request);
|
||||
var items =
|
||||
SearchGetTypeHierarchyResult.fromResponse(response).hierarchyItems;
|
||||
expect(items, isNull);
|
||||
|
@ -1373,7 +1373,7 @@ enum E with M {
|
|||
{bool? superOnly}) async {
|
||||
await waitForTasksFinished();
|
||||
var request = _createGetTypeHierarchyRequest(search, superOnly: superOnly);
|
||||
var response = await serverChannel.sendRequest(request);
|
||||
var response = await serverChannel.simulateRequestFromClient(request);
|
||||
return SearchGetTypeHierarchyResult.fromResponse(response).hierarchyItems;
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ class SocketServerTest {
|
|||
expect(channel2.responsesReceived[0].error!.code,
|
||||
equals(RequestErrorCode.SERVER_ALREADY_STARTED));
|
||||
channel2
|
||||
.sendRequest(ServerShutdownParams().toRequest('0'))
|
||||
.simulateRequestFromClient(ServerShutdownParams().toRequest('0'))
|
||||
.then((Response response) {
|
||||
expect(response.id, equals('0'));
|
||||
var error = response.error!;
|
||||
|
@ -56,7 +56,7 @@ class SocketServerTest {
|
|||
expect(
|
||||
channel.notificationsReceived[0].event, SERVER_NOTIFICATION_CONNECTED);
|
||||
return channel
|
||||
.sendRequest(ServerShutdownParams().toRequest('0'))
|
||||
.simulateRequestFromClient(ServerShutdownParams().toRequest('0'))
|
||||
.then((Response response) {
|
||||
expect(response.id, equals('0'));
|
||||
expect(response.error, isNull);
|
||||
|
|
Loading…
Reference in a new issue