@@ -118,6 +118,9 @@ class LspClientCapabilities {
118
118
/// A set of commands that exist on the client that the server may call.
119
119
final Set <String > supportedCommands;
120
120
121
+ /// User-friendly error messages from parsing the experimental capabilities.
122
+ final List <String > experimentalCapabilitiesErrors;
123
+
121
124
factory LspClientCapabilities (ClientCapabilities raw) {
122
125
var workspace = raw.workspace;
123
126
var workspaceEdit = workspace? .workspaceEdit;
@@ -137,8 +140,6 @@ class LspClientCapabilities {
137
140
var definition = textDocument? .definition;
138
141
var typeDefinition = textDocument? .typeDefinition;
139
142
var workspaceSymbol = workspace? .symbol;
140
- var experimental = _mapOrEmpty (raw.experimental);
141
- var experimentalActions = _mapOrEmpty (experimental['dartCodeAction' ]);
142
143
143
144
var applyEdit = workspace? .applyEdit ?? false ;
144
145
var codeActionKinds =
@@ -184,30 +185,8 @@ class LspClientCapabilities {
184
185
var workDoneProgress = raw.window? .workDoneProgress ?? false ;
185
186
var workspaceSymbolKinds = _listToSet (workspaceSymbol? .symbolKind? .valueSet,
186
187
defaults: defaultSupportedSymbolKinds);
187
- var experimentalSnippetTextEdit = experimental['snippetTextEdit' ] == true ;
188
- var commandParameterSupport =
189
- _mapOrEmpty (experimentalActions['commandParameterSupport' ]);
190
- var commandParameterSupportedKinds =
191
- _listToSet (commandParameterSupport['supportedKinds' ] as List ? )
192
- .cast <String >();
193
- var supportsDartExperimentalTextDocumentContentProvider =
194
- (experimental[dartExperimentalTextDocumentContentProviderKey] ??
195
- experimental[
196
- dartExperimentalTextDocumentContentProviderLegacyKey]) !=
197
- null ;
198
- var supportedCommands =
199
- _listToSet (experimental['commands' ] as List ? ).cast <String >();
200
188
201
- /// At the time of writing (2023-02-01) there is no official capability for
202
- /// supporting 'showMessageRequest' because LSP assumed all clients
203
- /// supported it.
204
- ///
205
- /// This turned out to not be the case, so to avoid sending prompts that
206
- /// might not be seen, we will only use this functionality if we _know_ the
207
- /// client supports it via a custom flag in 'experimental' that is passed by
208
- /// the Dart-Code VS Code extension since version v3.58.0 (2023-01-25).
209
- var supportsShowMessageRequest =
210
- experimental['supportsWindowShowMessageRequest' ] == true ;
189
+ var experimental = _ExperimentalClientCapabilities .parse (raw.experimental);
211
190
212
191
return LspClientCapabilities ._(
213
192
raw,
@@ -241,12 +220,14 @@ class LspClientCapabilities {
241
220
completionLabelDetails: completionLabelDetails,
242
221
completionDefaultEditRange: completionDefaultEditRange,
243
222
completionDefaultTextMode: completionDefaultTextMode,
244
- experimentalSnippetTextEdit: experimentalSnippetTextEdit,
245
- codeActionCommandParameterSupportedKinds: commandParameterSupportedKinds,
246
- supportsShowMessageRequest: supportsShowMessageRequest,
223
+ experimentalSnippetTextEdit: experimental.snippetTextEdit,
224
+ codeActionCommandParameterSupportedKinds:
225
+ experimental.commandParameterKinds,
226
+ supportsShowMessageRequest: experimental.showMessageRequest,
247
227
supportsDartExperimentalTextDocumentContentProvider:
248
- supportsDartExperimentalTextDocumentContentProvider,
249
- supportedCommands: supportedCommands,
228
+ experimental.dartTextDocumentContentProvider,
229
+ supportedCommands: experimental.commands,
230
+ experimentalCapabilitiesErrors: experimental.errors,
250
231
);
251
232
}
252
233
@@ -287,6 +268,7 @@ class LspClientCapabilities {
287
268
required this .supportsShowMessageRequest,
288
269
required this .supportsDartExperimentalTextDocumentContentProvider,
289
270
required this .supportedCommands,
271
+ required this .experimentalCapabilitiesErrors,
290
272
});
291
273
292
274
/// Converts a list to a `Set` , returning null if the list is null.
@@ -300,8 +282,120 @@ class LspClientCapabilities {
300
282
static Set <T > _listToSet <T >(List <T >? items, {Set <T > defaults = const {}}) {
301
283
return items != null ? {...items} : defaults;
302
284
}
285
+ }
286
+
287
+ /// A helper for parsing experimental capabilities and collecting any errors
288
+ /// because their values do not match the types expected by the server.
289
+ class _ExperimentalClientCapabilities {
290
+ /// User-friendly error messages from parsing the experimental capabilities.
291
+ final List <String > errors;
292
+
293
+ final bool snippetTextEdit;
294
+ final Set <String > commandParameterKinds;
295
+ final bool dartTextDocumentContentProvider;
296
+ final Set <String > commands;
297
+ final bool showMessageRequest;
298
+
299
+ _ExperimentalClientCapabilities ({
300
+ required this .snippetTextEdit,
301
+ required this .commandParameterKinds,
302
+ required this .dartTextDocumentContentProvider,
303
+ required this .commands,
304
+ required this .showMessageRequest,
305
+ required this .errors,
306
+ });
307
+
308
+ /// Parse the experimental capabilities.
309
+ ///
310
+ /// Unlike the capabilities above the spec doesn't define any types for
311
+ /// these, so we may see types we don't expect (whereas the above would have
312
+ /// failed to deserialize if the types are invalid). So, check the types
313
+ /// carefully and report a warning to the client if something looks wrong.
314
+ ///
315
+ /// Example: https://github.com/dart-lang/sdk/issues/55935
316
+ factory _ExperimentalClientCapabilities .parse (Object ? raw) {
317
+ var errors = < String > [];
318
+
319
+ /// Helper to ensure [object] is type [T] and otherwise records an error in
320
+ /// [errors] and returns `null` .
321
+ T ? expectType <T >(String suffix, Object ? object, [String ? typeDescription]) {
322
+ if (object is ! T ) {
323
+ errors.add (
324
+ 'ClientCapabilities.experimental$suffix must be a ${typeDescription ?? T }' );
325
+ return null ;
326
+ }
327
+ return object;
328
+ }
329
+
330
+ var expectMap = expectType< Map <String , Object ?>? > ;
331
+ var expectBool = expectType< bool ? > ;
332
+ var expectString = expectType< String > ;
333
+
334
+ /// Helper to expect a nullable list of strings and return them as a set.
335
+ Set <String >? expectNullableStringSet (String name, Object ? object) {
336
+ return expectType <List <Object ?>?>(name, object, 'List<String>?' )
337
+ ? .map ((item) => expectString ('$name []' , item))
338
+ .nonNulls
339
+ .toSet ();
340
+ }
341
+
342
+ var experimental = expectMap ('' , raw) ?? const {};
303
343
304
- static Map <String , Object ?> _mapOrEmpty (Object ? item) {
305
- return item is Map <String , Object ?> ? item : const {};
344
+ // Snippets.
345
+ var snippetTextEdit = expectBool (
346
+ '.snippetTextEdit' ,
347
+ experimental['snippetTextEdit' ],
348
+ );
349
+
350
+ // Refactor command parameters.
351
+ var experimentalActions = expectMap (
352
+ '.dartCodeAction' ,
353
+ experimental['dartCodeAction' ],
354
+ );
355
+ experimentalActions ?? = const {};
356
+ var commandParameters = expectMap (
357
+ '.dartCodeAction.commandParameterSupport' ,
358
+ experimentalActions['commandParameterSupport' ],
359
+ );
360
+ commandParameters ?? = {};
361
+ var commandParameterKinds = expectNullableStringSet (
362
+ '.dartCodeAction.commandParameterSupport.supportedKinds' ,
363
+ commandParameters['supportedKinds' ],
364
+ );
365
+
366
+ // Macro/Augmentation content.
367
+ var dartContentValue =
368
+ experimental[dartExperimentalTextDocumentContentProviderKey] ??
369
+ experimental[dartExperimentalTextDocumentContentProviderLegacyKey];
370
+ var dartTextDocumentContentProvider = expectBool (
371
+ '.$dartExperimentalTextDocumentContentProviderKey ' ,
372
+ dartContentValue,
373
+ );
374
+
375
+ // Executable commands.
376
+ var commands =
377
+ expectNullableStringSet ('.commands' , experimental['commands' ]);
378
+
379
+ /// At the time of writing (2023-02-01) there is no official capability for
380
+ /// supporting 'showMessageRequest' because LSP assumed all clients
381
+ /// supported it.
382
+ ///
383
+ /// This turned out to not be the case, so to avoid sending prompts that
384
+ /// might not be seen, we will only use this functionality if we _know_ the
385
+ /// client supports it via a custom flag in 'experimental' that is passed by
386
+ /// the Dart-Code VS Code extension since version v3.58.0 (2023-01-25).
387
+ var showMessageRequest = expectBool (
388
+ '.supportsWindowShowMessageRequest' ,
389
+ experimental['supportsWindowShowMessageRequest' ],
390
+ );
391
+
392
+ return _ExperimentalClientCapabilities (
393
+ snippetTextEdit: snippetTextEdit ?? false ,
394
+ commandParameterKinds: commandParameterKinds ?? {},
395
+ dartTextDocumentContentProvider: dartTextDocumentContentProvider ?? false ,
396
+ commands: commands ?? {},
397
+ showMessageRequest: showMessageRequest ?? false ,
398
+ errors: errors,
399
+ );
306
400
}
307
401
}
0 commit comments