Skip to content

Commit a0b6a48

Browse files
DanTupCommit Queue
authored andcommitted
[analysis_server] Allow legacy clients to set LSP capabilities in setClientCapabilities
This adds a new field to the existing (legacy) setClientCapabilities parameters that accepts an LSP ClientCapabilities. This will allow a legacy client to indicate that it supports things like the `workspace/applyEdit` reverse-request. Change-Id: Ia3b75c701f1699c92f902e058daec4844ce664fa Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/404106 Reviewed-by: Brian Wilkerson <[email protected]> Reviewed-by: Phil Quitslund <[email protected]> Commit-Queue: Phil Quitslund <[email protected]>
1 parent 9b71817 commit a0b6a48

File tree

12 files changed

+209
-14
lines changed

12 files changed

+209
-14
lines changed

pkg/analysis_server/doc/api.html

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@
110110
<body>
111111
<h1>Analysis Server API Specification</h1>
112112
<h1 style="color:#999999">Version
113-
1.38.0
113+
1.39.0
114114
</h1>
115115
<p>
116116
This document contains a specification of the API provided by the
@@ -245,6 +245,10 @@ <h3>Enumerations</h3>
245245
ignoring the item or treating it with some default/fallback handling.
246246
</p>
247247
<h3>Changelog</h3>
248+
<h4>1.39.0</h4>
249+
<ul>
250+
<li>Added a new <tt>lspCapabilities</tt> field to the <tt>setClientCapabilities</tt> parameters to allow clients to provide their LSP capabilities. These capabilities can indicate that a client can support <tt>lsp.handle</tt> requests in the server-to-client direction.</li>
251+
</ul>
248252
<h4>1.38.0</h4>
249253
<ul>
250254
<li>Deprecated the <tt>analytics.enable</tt> request.</li>
@@ -581,6 +585,7 @@ <h4>parameters:</h4><dl><dt class="field"><b>id: String</b></dt><dd>
581585
"params": {
582586
"<b>requests</b>": List&lt;String&gt;
583587
"<b>supportsUris</b>": <span style="color:#999999">optional</span> bool
588+
"<b>lspCapabilities</b>": <span style="color:#999999">optional</span> object
584589
}
585590
}</pre><br><pre>response: {
586591
"id": String
@@ -627,6 +632,17 @@ <h4>parameters:</h4><dl><dt class="field"><b>requests: List&lt;String&gt;</b></d
627632
<p>
628633
LSP notifications are automatically enabled when the client sets this capability.
629634
</p>
635+
</dd><dt class="field"><b>lspCapabilities: object<span style="color:#999999"> (optional)</span></b></dt><dd>
636+
637+
<p>
638+
LSP capabilities of the client as defined by the Language Server Protocol specification.
639+
</p>
640+
<p>
641+
If custom LSP capabilities are to be used, the setClientCapabilities request should be called before any LSP requests are made to the server.
642+
</p>
643+
<p>
644+
If LSP capabilities are not provided or no setClientCapabilities request is made, a very basic set of capabilities will be assumed.
645+
</p>
630646
</dd></dl></dd><dt class="request"><a name="request_server.openUrlRequest">server.openUrlRequest</a></dt><dd><div class="box"><pre>request: {
631647
"id": String
632648
"method": "server.openUrlRequest"

pkg/analysis_server/lib/protocol/protocol_constants.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
// To regenerate the file, use the script
77
// "pkg/analysis_server/tool/spec/generate_files".
88

9-
const String PROTOCOL_VERSION = '1.38.0';
9+
const String PROTOCOL_VERSION = '1.39.0';
1010

1111
const String ANALYSIS_NOTIFICATION_ANALYZED_FILES = 'analysis.analyzedFiles';
1212
const String ANALYSIS_NOTIFICATION_ANALYZED_FILES_DIRECTORIES = 'directories';
@@ -335,6 +335,8 @@ const String SERVER_REQUEST_OPEN_URL_REQUEST = 'server.openUrlRequest';
335335
const String SERVER_REQUEST_OPEN_URL_REQUEST_URL = 'url';
336336
const String SERVER_REQUEST_SET_CLIENT_CAPABILITIES =
337337
'server.setClientCapabilities';
338+
const String SERVER_REQUEST_SET_CLIENT_CAPABILITIES_LSP_CAPABILITIES =
339+
'lspCapabilities';
338340
const String SERVER_REQUEST_SET_CLIENT_CAPABILITIES_REQUESTS = 'requests';
339341
const String SERVER_REQUEST_SET_CLIENT_CAPABILITIES_SUPPORTS_URIS =
340342
'supportsUris';

pkg/analysis_server/lib/protocol/protocol_generated.dart

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19093,6 +19093,7 @@ enum ServerService {
1909319093
/// {
1909419094
/// "requests": List<String>
1909519095
/// "supportsUris": optional bool
19096+
/// "lspCapabilities": optional object
1909619097
/// }
1909719098
///
1909819099
/// Clients may not extend, implement or mix-in this class.
@@ -19125,7 +19126,21 @@ class ServerSetClientCapabilitiesParams implements RequestParams {
1912519126
/// capability.
1912619127
bool? supportsUris;
1912719128

19128-
ServerSetClientCapabilitiesParams(this.requests, {this.supportsUris});
19129+
/// LSP capabilities of the client as defined by the Language Server Protocol
19130+
/// specification.
19131+
///
19132+
/// If custom LSP capabilities are to be used, the setClientCapabilities
19133+
/// request should be called before any LSP requests are made to the server.
19134+
///
19135+
/// If LSP capabilities are not provided or no setClientCapabilities request
19136+
/// is made, a very basic set of capabilities will be assumed.
19137+
Object? lspCapabilities;
19138+
19139+
ServerSetClientCapabilitiesParams(
19140+
this.requests, {
19141+
this.supportsUris,
19142+
this.lspCapabilities,
19143+
});
1912919144

1913019145
factory ServerSetClientCapabilitiesParams.fromJson(
1913119146
JsonDecoder jsonDecoder,
@@ -19152,9 +19167,14 @@ class ServerSetClientCapabilitiesParams implements RequestParams {
1915219167
json['supportsUris'],
1915319168
);
1915419169
}
19170+
Object? lspCapabilities;
19171+
if (json.containsKey('lspCapabilities')) {
19172+
lspCapabilities = json['lspCapabilities'] as Object;
19173+
}
1915519174
return ServerSetClientCapabilitiesParams(
1915619175
requests,
1915719176
supportsUris: supportsUris,
19177+
lspCapabilities: lspCapabilities,
1915819178
);
1915919179
} else {
1916019180
throw jsonDecoder.mismatch(
@@ -19187,6 +19207,10 @@ class ServerSetClientCapabilitiesParams implements RequestParams {
1918719207
if (supportsUris != null) {
1918819208
result['supportsUris'] = supportsUris;
1918919209
}
19210+
var lspCapabilities = this.lspCapabilities;
19211+
if (lspCapabilities != null) {
19212+
result['lspCapabilities'] = lspCapabilities;
19213+
}
1919019214
return result;
1919119215
}
1919219216

@@ -19213,13 +19237,15 @@ class ServerSetClientCapabilitiesParams implements RequestParams {
1921319237
other.requests,
1921419238
(String a, String b) => a == b,
1921519239
) &&
19216-
supportsUris == other.supportsUris;
19240+
supportsUris == other.supportsUris &&
19241+
lspCapabilities == other.lspCapabilities;
1921719242
}
1921819243
return false;
1921919244
}
1922019245

1922119246
@override
19222-
int get hashCode => Object.hash(Object.hashAll(requests), supportsUris);
19247+
int get hashCode =>
19248+
Object.hash(Object.hashAll(requests), supportsUris, lspCapabilities);
1922319249
}
1922419250

1922519251
/// server.setClientCapabilities result

pkg/analysis_server/lib/src/handler/legacy/server_set_client_capabilities.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ class ServerSetClientCapabilitiesHandler extends LegacyHandler {
2929
} on RequestFailure catch (exception) {
3030
sendResponse(exception.response);
3131
return;
32+
} on RequestError catch (error) {
33+
sendResponse(Response(request.id, error: error));
34+
return;
3235
}
3336
sendResult(ServerSetClientCapabilitiesResult());
3437
}

pkg/analysis_server/lib/src/legacy_analysis_server.dart

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ import 'package:analyzer_plugin/src/utilities/client_uri_converter.dart';
111111
import 'package:analyzer_plugin/src/utilities/navigation/navigation.dart';
112112
import 'package:analyzer_plugin/src/utilities/navigation/navigation_dart.dart';
113113
import 'package:http/http.dart' as http;
114+
import 'package:language_server_protocol/json_parsing.dart' as lsp;
114115
import 'package:meta/meta.dart';
115116
import 'package:telemetry/crash_reporting.dart';
116117
import 'package:watcher/watcher.dart';
@@ -292,8 +293,8 @@ class LegacyAnalysisServer extends AnalysisServer {
292293
ServerSetClientCapabilitiesParams _clientCapabilities =
293294
ServerSetClientCapabilitiesParams([]);
294295

295-
@override
296-
final editorClientCapabilities = lsp.fixedBasicLspClientCapabilities;
296+
/// See [editorClientCapabilities].
297+
var _editorClientCapabilities = lsp.fixedBasicLspClientCapabilities;
297298

298299
@override
299300
final lsp.LspClientConfiguration lspClientConfiguration;
@@ -441,8 +442,28 @@ class LegacyAnalysisServer extends AnalysisServer {
441442
} else {
442443
uriConverter = ClientUriConverter.noop(resourceProvider.pathContext);
443444
}
445+
446+
if (capabilities.lspCapabilities
447+
case Map<Object?, Object?> lspCapabilities) {
448+
// First validate the capabilities so we can get a better message if it's
449+
// invalid.
450+
var reporter = lsp.LspJsonReporter();
451+
if (!lsp.ClientCapabilities.canParse(lspCapabilities, reporter)) {
452+
throw RequestError(
453+
RequestErrorCode.INVALID_PARAMETER,
454+
"The 'lspCapabilities' parameter was invalid: ${reporter.errors.join(', ')}",
455+
);
456+
}
457+
458+
_editorClientCapabilities = lsp.LspClientCapabilities(
459+
lsp.ClientCapabilities.fromJson(lspCapabilities.cast<String, Object>()),
460+
);
461+
}
444462
}
445463

464+
@override
465+
get editorClientCapabilities => _editorClientCapabilities;
466+
446467
/// The [Future] that completes when analysis is complete.
447468
Future<void> get onAnalysisComplete {
448469
if (isAnalysisComplete()) {

pkg/analysis_server/test/domain_server_test.dart

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'dart:async';
66

7+
import 'package:analysis_server/lsp_protocol/protocol.dart' as lsp;
78
import 'package:analysis_server/protocol/protocol.dart';
89
import 'package:analysis_server/protocol/protocol_constants.dart';
910
import 'package:analysis_server/protocol/protocol_generated.dart'
@@ -100,6 +101,59 @@ class ServerDomainTest extends PubPackageAnalysisServerTest {
100101
await responseFuture;
101102
}
102103

104+
Future<void> test_setClientCapabilities_lspCapabilities() async {
105+
// Test an arbitrary set of capabilities.
106+
var capabilities = lsp.ClientCapabilities(
107+
textDocument: lsp.TextDocumentClientCapabilities(
108+
hover: lsp.HoverClientCapabilities(
109+
contentFormat: [lsp.MarkupKind.PlainText],
110+
),
111+
),
112+
workspace: lsp.WorkspaceClientCapabilities(
113+
applyEdit: true,
114+
workspaceEdit: lsp.WorkspaceEditClientCapabilities(
115+
documentChanges: true,
116+
resourceOperations: [lsp.ResourceOperationKind.Create],
117+
),
118+
),
119+
);
120+
121+
var request = ServerSetClientCapabilitiesParams(
122+
[],
123+
lspCapabilities: capabilities.toJson(),
124+
).toRequest('1', clientUriConverter: server.uriConverter);
125+
126+
await handleSuccessfulRequest(request);
127+
var effectiveCapabilities = server.editorClientCapabilities!;
128+
expect(
129+
effectiveCapabilities.hoverContentFormats,
130+
equals([lsp.MarkupKind.PlainText]),
131+
);
132+
expect(effectiveCapabilities.applyEdit, isTrue);
133+
expect(effectiveCapabilities.documentChanges, isTrue);
134+
expect(effectiveCapabilities.createResourceOperations, isTrue);
135+
}
136+
137+
Future<void> test_setClientCapabilities_lspCapabilities_invalid() async {
138+
var request = ServerSetClientCapabilitiesParams(
139+
[],
140+
lspCapabilities: {
141+
'textDocument': 1, // Not valid
142+
},
143+
).toRequest('1', clientUriConverter: server.uriConverter);
144+
145+
var response = await handleRequest(request);
146+
expect(
147+
response,
148+
isResponseFailure('1', RequestErrorCode.INVALID_PARAMETER),
149+
);
150+
expect(
151+
response.error!.message,
152+
"The 'lspCapabilities' parameter was invalid:"
153+
' textDocument must be of type TextDocumentClientCapabilities',
154+
);
155+
}
156+
103157
Future<void> test_setClientCapabilities_requests() async {
104158
var requestId = -1;
105159

pkg/analysis_server/test/integration/support/integration_test_methods.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,27 @@ abstract class IntegrationTest {
132132
///
133133
/// LSP notifications are automatically enabled when the client sets this
134134
/// capability.
135+
///
136+
/// lspCapabilities: object (optional)
137+
///
138+
/// LSP capabilities of the client as defined by the Language Server
139+
/// Protocol specification.
140+
///
141+
/// If custom LSP capabilities are to be used, the setClientCapabilities
142+
/// request should be called before any LSP requests are made to the
143+
/// server.
144+
///
145+
/// If LSP capabilities are not provided or no setClientCapabilities
146+
/// request is made, a very basic set of capabilities will be assumed.
135147
Future<void> sendServerSetClientCapabilities(
136148
List<String> requests, {
137149
bool? supportsUris,
150+
Object? lspCapabilities,
138151
}) async {
139152
var params = ServerSetClientCapabilitiesParams(
140153
requests,
141154
supportsUris: supportsUris,
155+
lspCapabilities: lspCapabilities,
142156
).toJson(clientUriConverter: uriConverter);
143157
var result = await server.send('server.setClientCapabilities', params);
144158
outOfTestExpect(result, isNull);
@@ -2989,6 +3003,11 @@ abstract class IntegrationTest {
29893003

29903004
/// Call an LSP handler. Message can be requests or notifications.
29913005
///
3006+
/// This request can be called in either direction, either by the client to
3007+
/// the server, or by the server to the client. The server will only call the
3008+
/// client if the client has indicated it supports the associated LSP request
3009+
/// via `lspCapabilities` in the `setClientCapabilities` request.
3010+
///
29923011
/// Parameters
29933012
///
29943013
/// lspMessage: object

pkg/analysis_server/test/integration/support/protocol_matchers.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3487,12 +3487,13 @@ final Matcher isServerOpenUrlRequestResult = isNull;
34873487
/// {
34883488
/// "requests": List<String>
34893489
/// "supportsUris": optional bool
3490+
/// "lspCapabilities": optional object
34903491
/// }
34913492
final Matcher isServerSetClientCapabilitiesParams = LazyMatcher(
34923493
() => MatchesJsonObject(
34933494
'server.setClientCapabilities params',
34943495
{'requests': isListOf(isString)},
3495-
optionalFields: {'supportsUris': isBool},
3496+
optionalFields: {'supportsUris': isBool, 'lspCapabilities': isObject},
34963497
),
34973498
);
34983499

pkg/analysis_server/tool/spec/generated/java/AnalysisServer.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,11 @@ public interface AnalysisServer {
876876
*
877877
* Call an LSP handler. Message can be requests or notifications.
878878
*
879+
* This request can be called in either direction, either by the client to the server, or by the
880+
* server to the client. The server will only call the client if the client has indicated it
881+
* supports the associated LSP request via <code>lspCapabilities</code> in the
882+
* <code>setClientCapabilities</code> request.
883+
*
879884
* @param lspMessage The LSP RequestMessage.
880885
*/
881886
public void lsp_handle(Object lspMessage, HandleConsumer consumer);
@@ -1041,8 +1046,13 @@ public interface AnalysisServer {
10411046
* can fetch the file contents for URIs with custom schemes (and receive modification
10421047
* events) through the LSP protocol (see the "lsp" domain). LSP notifications are
10431048
* automatically enabled when the client sets this capability.
1049+
* @param lspCapabilities LSP capabilities of the client as defined by the Language Server Protocol
1050+
* specification. If custom LSP capabilities are to be used, the setClientCapabilities
1051+
* request should be called before any LSP requests are made to the server. If LSP
1052+
* capabilities are not provided or no setClientCapabilities request is made, a very basic
1053+
* set of capabilities will be assumed.
10441054
*/
1045-
public void server_setClientCapabilities(List<String> requests, boolean supportsUris);
1055+
public void server_setClientCapabilities(List<String> requests, boolean supportsUris, Object lspCapabilities);
10461056

10471057
/**
10481058
* {@code server.setSubscriptions}

pkg/analysis_server/tool/spec/spec_input.html

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<body>
88
<h1>Analysis Server API Specification</h1>
99
<h1 style="color:#999999">Version
10-
<version>1.38.0</version>
10+
<version>1.39.0</version>
1111
</h1>
1212
<p>
1313
This document contains a specification of the API provided by the
@@ -142,6 +142,10 @@ <h3>Enumerations</h3>
142142
ignoring the item or treating it with some default/fallback handling.
143143
</p>
144144
<h3>Changelog</h3>
145+
<h4>1.39.0</h4>
146+
<ul>
147+
<li>Added a new <tt>lspCapabilities</tt> field to the <tt>setClientCapabilities</tt> parameters to allow clients to provide their LSP capabilities. These capabilities can indicate that a client can support <tt>lsp.handle</tt> requests in the server-to-client direction.</li>
148+
</ul>
145149
<h4>1.38.0</h4>
146150
<ul>
147151
<li>Deprecated the <tt>analytics.enable</tt> request.</li>
@@ -427,6 +431,18 @@ <h4>Options</h4>
427431
LSP notifications are automatically enabled when the client sets this capability.
428432
</p>
429433
</field>
434+
<field name="lspCapabilities" optional="true">
435+
<ref>object</ref>
436+
<p>
437+
LSP capabilities of the client as defined by the Language Server Protocol specification.
438+
</p>
439+
<p>
440+
If custom LSP capabilities are to be used, the setClientCapabilities request should be called before any LSP requests are made to the server.
441+
</p>
442+
<p>
443+
If LSP capabilities are not provided or no setClientCapabilities request is made, a very basic set of capabilities will be assumed.
444+
</p>
445+
</field>
430446
</params>
431447
</request>
432448
<request method="openUrlRequest">
@@ -3436,6 +3452,9 @@ <h4>Options</h4>
34363452
<p>
34373453
Call an LSP handler. Message can be requests or notifications.
34383454
</p>
3455+
<p>
3456+
This request can be called in either direction, either by the client to the server, or by the server to the client. The server will only call the client if the client has indicated it supports the associated LSP request via <tt>lspCapabilities</tt> in the <tt>setClientCapabilities</tt> request.
3457+
</p>
34393458
<params>
34403459
<field name="lspMessage">
34413460
<p>

0 commit comments

Comments
 (0)