@@ -74,6 +74,26 @@ mixin LspEditHelpersMixin {
7474 }
7575}
7676
77+ /// Helpers to simplify handling LSP notifications.
78+ mixin LspNotificationHelpersMixin {
79+ /// A stream of [DartTextDocumentContentDidChangeParams] for any
80+ /// `dart/textDocumentContentDidChange` notifications.
81+ Stream <DartTextDocumentContentDidChangeParams >
82+ get dartTextDocumentContentDidChangeNotifications => notificationsFromServer
83+ .where (
84+ (notification) =>
85+ notification.method ==
86+ CustomMethods .dartTextDocumentContentDidChange,
87+ )
88+ .map (
89+ (message) => DartTextDocumentContentDidChangeParams .fromJson (
90+ message.params as Map <String , Object ?>,
91+ ),
92+ );
93+
94+ Stream <NotificationMessage > get notificationsFromServer;
95+ }
96+
7797mixin LspProgressNotificationsMixin {
7898 Stream <NotificationMessage > get notificationsFromServer;
7999
@@ -143,25 +163,6 @@ mixin LspRequestHelpersMixin {
143163 /// should not be validated as being created by the server first.
144164 final clientProvidedTestWorkDoneToken = ProgressToken .t2 ('client-test' );
145165
146- /// A stream of [DartTextDocumentContentDidChangeParams] for any
147- /// `dart/textDocumentContentDidChange` notifications.
148- Stream <DartTextDocumentContentDidChangeParams >
149- get dartTextDocumentContentDidChangeNotifications => notificationsFromServer
150- .where (
151- (notification) =>
152- notification.method ==
153- CustomMethods .dartTextDocumentContentDidChange,
154- )
155- .map (
156- (message) => DartTextDocumentContentDidChangeParams .fromJson (
157- message.params as Map <String , Object ?>,
158- ),
159- );
160-
161- Stream <NotificationMessage > get notificationsFromServer;
162-
163- Stream <RequestMessage > get requestsFromServer;
164-
165166 Future <List <CallHierarchyIncomingCall >?> callHierarchyIncoming (
166167 CallHierarchyItem item,
167168 ) {
@@ -237,29 +238,6 @@ mixin LspRequestHelpersMixin {
237238 void expect (Object ? actual, Matcher matcher, {String ? reason}) =>
238239 test.expect (actual, matcher, reason: reason);
239240
240- /// Expects a [method] request from the server after executing [f] .
241- Future <RequestMessage > expectRequest (
242- Method method,
243- FutureOr <void > Function () f, {
244- Duration timeout = const Duration (seconds: 5 ),
245- }) async {
246- var firstRequest = requestsFromServer.firstWhere ((n) => n.method == method);
247- await f ();
248-
249- var requestFromServer = await firstRequest.timeout (
250- timeout,
251- onTimeout:
252- () =>
253- throw TimeoutException (
254- 'Did not receive the expected $method request from the server in the timeout period' ,
255- timeout,
256- ),
257- );
258-
259- expect (requestFromServer, isNotNull);
260- return requestFromServer;
261- }
262-
263241 Future <T > expectSuccessfulResponseTo <T , R >(
264242 RequestMessage request,
265243 T Function (R ) fromJson,
@@ -875,71 +853,6 @@ mixin LspRequestHelpersMixin {
875853 );
876854 }
877855
878- /// Executes [f] then waits for a request of type [method] from the server which
879- /// is passed to [handler] to process, then waits for (and returns) the
880- /// response to the original request.
881- ///
882- /// This is used for testing things like code actions, where the client initiates
883- /// a request but the server does not respond to it until it's sent its own
884- /// request to the client and it received a response.
885- ///
886- /// Client Server
887- /// 1. |- Req: textDocument/codeAction ->
888- /// 1. <- Resp: textDocument/codeAction -|
889- ///
890- /// 2. |- Req: workspace/executeCommand ->
891- /// 3. <- Req: textDocument/applyEdits -|
892- /// 3. |- Resp: textDocument/applyEdits ->
893- /// 2. <- Resp: workspace/executeCommand -|
894- ///
895- /// Request 2 from the client is not responded to until the server has its own
896- /// response to the request it sends (3).
897- Future <T > handleExpectedRequest <T , R , RR >(
898- Method method,
899- R Function (Map <String , dynamic >) fromJson,
900- Future <T > Function () f, {
901- required FutureOr <RR > Function (R ) handler,
902- Duration timeout = const Duration (seconds: 5 ),
903- }) async {
904- late Future <T > outboundRequest;
905- Object ? outboundRequestError;
906-
907- // Run [f] and wait for the incoming request from the server.
908- var incomingRequest = await expectRequest (method, () {
909- // Don't return/await the response yet, as this may not complete until
910- // after we have handled the request that comes from the server.
911- outboundRequest = f ();
912-
913- // Because we don't await this future until "later", if it throws the
914- // error is treated as unhandled and will fail the test even if expected.
915- // Instead, capture the error and suppress it. But if we time out (in
916- // which case we will never return outboundRequest), then we'll raise this
917- // error.
918- outboundRequest.then (
919- (_) {},
920- onError: (e) {
921- outboundRequestError = e;
922- return null ;
923- },
924- );
925- }, timeout: timeout).catchError ((Object timeoutException) {
926- // We timed out waiting for the request from the server. Probably this is
927- // because our outbound request for some reason, so if we have an error
928- // for that, then throw it. Otherwise, propogate the timeout.
929- throw outboundRequestError ?? timeoutException;
930- }, test: (e) => e is TimeoutException );
931-
932- // Handle the request from the server and send the response back.
933- var clientsResponse = await handler (
934- fromJson (incomingRequest.params as Map <String , Object ?>),
935- );
936- respondTo (incomingRequest, clientsResponse);
937-
938- // Return a future that completes when the response to the original request
939- // (from [f]) returns.
940- return outboundRequest;
941- }
942-
943856 RequestMessage makeRequest (Method method, Object ? params) {
944857 var id = Either2 <int , String >.t1 (_id++ );
945858 return RequestMessage (
@@ -1014,24 +927,8 @@ mixin LspRequestHelpersMixin {
1014927 return expectSuccessfulResponseTo (request, CompletionItem .fromJson);
1015928 }
1016929
1017- /// Sends [responseParams] to the server as a successful response to
1018- /// a server-initiated [request] .
1019- void respondTo <T >(RequestMessage request, T responseParams) {
1020- sendResponseToServer (
1021- ResponseMessage (
1022- id: request.id,
1023- result: responseParams,
1024- jsonrpc: jsonRpcVersion,
1025- ),
1026- );
1027- }
1028-
1029930 Future <ResponseMessage > sendRequestToServer (RequestMessage request);
1030931
1031- /// Sends a ResponseMessage to the server, completing a reverse
1032- /// (server-to-client) request.
1033- void sendResponseToServer (ResponseMessage response);
1034-
1035932 Future <List <TypeHierarchyItem >?> typeHierarchySubtypes (
1036933 TypeHierarchyItem item,
1037934 ) {
@@ -1263,6 +1160,120 @@ mixin LspRequestHelpersMixin {
12631160 }
12641161}
12651162
1163+ /// Helpers to simplify handling LSP reverse-requests.
1164+ ///
1165+ /// The sending of responses must be supplied by the implementing class
1166+ /// via [sendResponseToServer] .
1167+ mixin LspReverseRequestHelpersMixin {
1168+ /// A stream of reverse-requests from the server that can be responded to via
1169+ /// [sendResponseToServer] .
1170+ Stream <RequestMessage > get requestsFromServer;
1171+
1172+ /// Expects a [method] request from the server after executing [f] .
1173+ Future <RequestMessage > expectRequest (
1174+ Method method,
1175+ FutureOr <void > Function () f, {
1176+ Duration timeout = const Duration (seconds: 5 ),
1177+ }) async {
1178+ var firstRequest = requestsFromServer.firstWhere ((n) => n.method == method);
1179+ await f ();
1180+
1181+ var requestFromServer = await firstRequest.timeout (
1182+ timeout,
1183+ onTimeout:
1184+ () =>
1185+ throw TimeoutException (
1186+ 'Did not receive the expected $method request from the server in the timeout period' ,
1187+ timeout,
1188+ ),
1189+ );
1190+
1191+ expect (requestFromServer, isNotNull);
1192+ return requestFromServer;
1193+ }
1194+
1195+ /// Executes [f] then waits for a request of type [method] from the server which
1196+ /// is passed to [handler] to process, then waits for (and returns) the
1197+ /// response to the original request.
1198+ ///
1199+ /// This is used for testing things like code actions, where the client initiates
1200+ /// a request but the server does not respond to it until it's sent its own
1201+ /// request to the client and it received a response.
1202+ ///
1203+ /// Client Server
1204+ /// 1. |- Req: textDocument/codeAction ->
1205+ /// 1. <- Resp: textDocument/codeAction -|
1206+ ///
1207+ /// 2. |- Req: workspace/executeCommand ->
1208+ /// 3. <- Req: textDocument/applyEdits -|
1209+ /// 3. |- Resp: textDocument/applyEdits ->
1210+ /// 2. <- Resp: workspace/executeCommand -|
1211+ ///
1212+ /// Request 2 from the client is not responded to until the server has its own
1213+ /// response to the request it sends (3).
1214+ Future <T > handleExpectedRequest <T , R , RR >(
1215+ Method method,
1216+ R Function (Map <String , dynamic >) fromJson,
1217+ Future <T > Function () f, {
1218+ required FutureOr <RR > Function (R ) handler,
1219+ Duration timeout = const Duration (seconds: 5 ),
1220+ }) async {
1221+ late Future <T > outboundRequest;
1222+ Object ? outboundRequestError;
1223+
1224+ // Run [f] and wait for the incoming request from the server.
1225+ var incomingRequest = await expectRequest (method, () {
1226+ // Don't return/await the response yet, as this may not complete until
1227+ // after we have handled the request that comes from the server.
1228+ outboundRequest = f ();
1229+
1230+ // Because we don't await this future until "later", if it throws the
1231+ // error is treated as unhandled and will fail the test even if expected.
1232+ // Instead, capture the error and suppress it. But if we time out (in
1233+ // which case we will never return outboundRequest), then we'll raise this
1234+ // error.
1235+ outboundRequest.then (
1236+ (_) {},
1237+ onError: (e) {
1238+ outboundRequestError = e;
1239+ return null ;
1240+ },
1241+ );
1242+ }, timeout: timeout).catchError ((Object timeoutException) {
1243+ // We timed out waiting for the request from the server. Probably this is
1244+ // because our outbound request for some reason, so if we have an error
1245+ // for that, then throw it. Otherwise, propogate the timeout.
1246+ throw outboundRequestError ?? timeoutException;
1247+ }, test: (e) => e is TimeoutException );
1248+
1249+ // Handle the request from the server and send the response back.
1250+ var clientsResponse = await handler (
1251+ fromJson (incomingRequest.params as Map <String , Object ?>),
1252+ );
1253+ respondTo (incomingRequest, clientsResponse);
1254+
1255+ // Return a future that completes when the response to the original request
1256+ // (from [f]) returns.
1257+ return outboundRequest;
1258+ }
1259+
1260+ /// Sends [responseParams] to the server as a successful response to
1261+ /// a server-initiated [request] .
1262+ void respondTo <T >(RequestMessage request, T responseParams) {
1263+ sendResponseToServer (
1264+ ResponseMessage (
1265+ id: request.id,
1266+ result: responseParams,
1267+ jsonrpc: jsonRpcVersion,
1268+ ),
1269+ );
1270+ }
1271+
1272+ /// Sends a ResponseMessage to the server, completing a reverse
1273+ /// (server-to-client) request.
1274+ void sendResponseToServer (ResponseMessage response);
1275+ }
1276+
12661277/// A mixin with helpers for verifying LSP edits in a given project.
12671278///
12681279/// Extends [LspEditHelpersMixin] with methods for accessing file state and
0 commit comments