Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit e436261

Browse files
DanTupcommit-bot@chromium.org
authored andcommitted
Add support for dynamic registration for LSP
Change-Id: I55c7b2f7d683acab0f6aa4bdbcba3b155d2de32c Change-Id: I6a0b13856053bd86e77637b9664c7fe41194c5f3 Change-Id: I7472cf93efc29585ccbcbd1e6c7b9f703872b3c5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/103532 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Danny Tuppeny <[email protected]>
1 parent 9690389 commit e436261

File tree

7 files changed

+311
-35
lines changed

7 files changed

+311
-35
lines changed

pkg/analysis_server/lib/lsp_protocol/protocol_generated.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8415,7 +8415,9 @@ class TextDocumentChangeRegistrationOptions
84158415
}
84168416
static TextDocumentChangeRegistrationOptions fromJson(
84178417
Map<String, dynamic> json) {
8418-
final syncKind = json['syncKind'];
8418+
final syncKind = json['syncKind'] != null
8419+
? TextDocumentSyncKind.fromJson(json['syncKind'])
8420+
: null;
84198421
final documentSelector = json['documentSelector']
84208422
?.map((item) => item != null ? DocumentFilter.fromJson(item) : null)
84218423
?.cast<DocumentFilter>()
@@ -8430,7 +8432,7 @@ class TextDocumentChangeRegistrationOptions
84308432

84318433
/// How documents are synced to the server. See TextDocumentSyncKind.Full and
84328434
/// TextDocumentSyncKind.Incremental.
8433-
final num syncKind;
8435+
final TextDocumentSyncKind syncKind;
84348436

84358437
Map<String, dynamic> toJson() {
84368438
Map<String, dynamic> __result = {};
@@ -8443,7 +8445,7 @@ class TextDocumentChangeRegistrationOptions
84438445
static bool canParse(Object obj) {
84448446
return obj is Map<String, dynamic> &&
84458447
obj.containsKey('syncKind') &&
8446-
obj['syncKind'] is num &&
8448+
TextDocumentSyncKind.canParse(obj['syncKind']) &&
84478449
obj.containsKey('documentSelector') &&
84488450
(obj['documentSelector'] == null ||
84498451
(obj['documentSelector'] is List &&

pkg/analysis_server/lib/src/lsp/constants.dart

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,36 @@
44

55
import 'package:analysis_server/lsp_protocol/protocol_generated.dart';
66

7+
/// Set the characters that will cause the editor to automatically
8+
/// trigger completion.
9+
/// TODO(dantup): There are several characters that we want to conditionally
10+
/// allow to trigger completion, but they can only be added when the completion
11+
/// provider is able to handle them in context:
12+
///
13+
/// { trigger if being typed in a string immediately after a $
14+
/// ' trigger if the opening quote for an import/export
15+
/// " trigger if the opening quote for an import/export
16+
/// / trigger if as part of a path in an import/export
17+
/// \ trigger if as part of a path in an import/export
18+
/// : don't trigger when typing case expressions (`case x:`)
19+
///
20+
/// Additionally, we need to prefix `filterText` on completion items
21+
/// with spaces for those that can follow whitespace (eg. `foo` in
22+
/// `myArg: foo`) to ensure they're not filtered away when the user
23+
/// types space.
24+
///
25+
/// See https://github.com/Dart-Code/Dart-Code/blob/68d1cd271e88a785570257d487adbdec17abd6a3/src/providers/dart_completion_item_provider.ts#L36-L64
26+
/// for the VS Code implementation of this.
27+
const dartCompletionTriggerCharacters = ['.', '=', '(', r'$'];
28+
29+
/// TODO(dantup): Signature help triggering is even more sensitive to
30+
/// bad chars, so we'll need to implement the logic described here:
31+
/// https://github.com/dart-lang/sdk/issues/34241
32+
const dartSignatureHelpTriggerCharacters = <String>[];
33+
34+
/// Characters to trigger formatting when format-on-type is enabled.
35+
const dartTypeFormattingCharacters = ['}', ';'];
36+
737
/// Constants for command IDs that are exchanged between LSP client/server.
838
abstract class Commands {
939
/// A list of all commands IDs that can be sent to the client to inform which
@@ -19,6 +49,11 @@ abstract class Commands {
1949
static const sendWorkspaceEdit = 'edit.sendWorkspaceEdit';
2050
}
2151

52+
abstract class CustomMethods {
53+
static const DiagnosticServer = const Method('dart/diagnosticServer');
54+
static const AnalyzerStatus = const Method(r'$/analyzerStatus');
55+
}
56+
2257
/// CodeActionKinds supported by the server that are not declared in the LSP spec.
2358
abstract class DartCodeActionKind {
2459
/// A list of all supported CodeAction kinds, supplied to the client during
@@ -61,8 +96,3 @@ abstract class ServerErrorCodes {
6196
/// if it crashes 5 times in the last 180 seconds."
6297
static const ClientServerInconsistentState = const ErrorCodes(-32010);
6398
}
64-
65-
abstract class CustomMethods {
66-
static const DiagnosticServer = const Method('dart/diagnosticServer');
67-
static const AnalyzerStatus = const Method(r'$/analyzerStatus');
68-
}

pkg/analysis_server/lib/src/lsp/handlers/handler_initialize.dart

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ class InitializeMessageHandler
6969
final renameOptionsSupport =
7070
params.capabilities.textDocument?.rename?.prepareSupport ?? false;
7171

72+
// When adding new capabilities to the server that may apply to specific file
73+
// types, it's important to update
74+
// [IntializedMessageHandler._performDynamicRegistration()] to notify
75+
// supporting clients of this. This avoids clients needing to hard-code the
76+
// list of what files types we support (and allows them to avoid sending
77+
// requests where we have only partial support for some types).
7278
server.capabilities = new ServerCapabilities(
7379
Either2<TextDocumentSyncOptions, num>.t1(new TextDocumentSyncOptions(
7480
true,
@@ -80,33 +86,10 @@ class InitializeMessageHandler
8086
true, // hoverProvider
8187
new CompletionOptions(
8288
true, // resolveProvider
83-
// Set the characters that will cause the editor to automatically
84-
// trigger completion.
85-
// TODO(dantup): There are several characters that we want to conditionally
86-
// allow to trigger completion, but they can only be added when the completion
87-
// provider is able to handle them in context:
88-
//
89-
// { trigger if being typed in a string immediately after a $
90-
// ' trigger if the opening quote for an import/export
91-
// " trigger if the opening quote for an import/export
92-
// / trigger if as part of a path in an import/export
93-
// \ trigger if as part of a path in an import/export
94-
// : don't trigger when typing case expressions (`case x:`)
95-
//
96-
// Additionally, we need to prefix `filterText` on completion items
97-
// with spaces for those that can follow whitespace (eg. `foo` in
98-
// `myArg: foo`) to ensure they're not filtered away when the user
99-
// types space.
100-
//
101-
// See https://github.com/Dart-Code/Dart-Code/blob/68d1cd271e88a785570257d487adbdec17abd6a3/src/providers/dart_completion_item_provider.ts#L36-L64
102-
// for the VS Code implementation of this.
103-
r'''.=($'''.split(''),
89+
dartCompletionTriggerCharacters,
10490
),
10591
new SignatureHelpOptions(
106-
// TODO(dantup): Signature help triggering is even more sensitive to
107-
// bad chars, so we'll need to implement the logic described here:
108-
// https://github.com/dart-lang/sdk/issues/34241
109-
[],
92+
dartSignatureHelpTriggerCharacters,
11093
),
11194
true, // definitionProvider
11295
null,
@@ -125,7 +108,8 @@ class InitializeMessageHandler
125108
null,
126109
true, // documentFormattingProvider
127110
false, // documentRangeFormattingProvider
128-
new DocumentOnTypeFormattingOptions('}', [';']),
111+
new DocumentOnTypeFormattingOptions(dartTypeFormattingCharacters.first,
112+
dartTypeFormattingCharacters.skip(1).toList()),
129113
renameOptionsSupport
130114
? Either2<bool, RenameOptions>.t2(new RenameOptions(true))
131115
: Either2<bool, RenameOptions>.t1(true),

pkg/analysis_server/lib/src/lsp/handlers/handler_initialized.dart

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

55
import 'package:analysis_server/lsp_protocol/protocol_generated.dart';
66
import 'package:analysis_server/lsp_protocol/protocol_special.dart';
7+
import 'package:analysis_server/src/lsp/constants.dart';
78
import 'package:analysis_server/src/lsp/handlers/handler_states.dart';
89
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
910
import 'package:analysis_server/src/lsp/lsp_analysis_server.dart';
@@ -30,10 +31,154 @@ class IntializedMessageHandler extends MessageHandler<InitializedParams, void> {
3031
suggestFromUnimportedLibraries,
3132
);
3233

34+
_performDynamicRegistration();
35+
3336
if (!onlyAnalyzeProjectsWithOpenFiles) {
3437
server.setAnalysisRoots(openWorkspacePaths);
3538
}
3639

3740
return success();
3841
}
42+
43+
/// If the client supports dynamic registrations we can tell it what methods
44+
/// we support for which documents. For example, this allows us to ask for
45+
/// file edits for .dart as well as pubspec.yaml but only get hover/completion
46+
/// calls for .dart. This functionality may not be supported by the client, in
47+
/// which case they will use the ServerCapabilities to know which methods we
48+
/// support and it will be up to them to decide which file types they will
49+
/// send requests for.
50+
Future<void> _performDynamicRegistration() async {
51+
final dartFiles = DocumentFilter('dart', 'file', null);
52+
final pubspecFile = DocumentFilter('yaml', 'file', '**/pubspec.yaml');
53+
final analysisOptionsFile =
54+
DocumentFilter('yaml', 'file', '**/analysis_options.yaml');
55+
final allTypes = [dartFiles, pubspecFile, analysisOptionsFile];
56+
57+
// TODO(dantup): When we support plugins, we will need to collect their
58+
// requirements too. For example, the Angular plugin might wish to add HTML
59+
// `DocumentFilter('html', 'file', null)` to many of these requests.
60+
61+
int _lastRegistrationId = 1;
62+
final registrations = <Registration>[];
63+
64+
/// Helper for creating registrations with IDs.
65+
void register(bool condition, Method method, [ToJsonable options]) {
66+
if (condition == true) {
67+
registrations.add(Registration(
68+
(_lastRegistrationId++).toString(), method.toJson(), options));
69+
}
70+
}
71+
72+
final textCapabilities = server.clientCapabilities?.textDocument;
73+
74+
register(
75+
textCapabilities?.synchronization?.dynamicRegistration,
76+
Method.textDocument_didOpen,
77+
TextDocumentRegistrationOptions(allTypes),
78+
);
79+
register(
80+
textCapabilities?.synchronization?.dynamicRegistration,
81+
Method.textDocument_didClose,
82+
TextDocumentRegistrationOptions(allTypes),
83+
);
84+
register(
85+
textCapabilities?.synchronization?.dynamicRegistration,
86+
Method.textDocument_didChange,
87+
TextDocumentChangeRegistrationOptions(
88+
TextDocumentSyncKind.Incremental, allTypes),
89+
);
90+
register(
91+
server.clientCapabilities?.textDocument?.completion?.dynamicRegistration,
92+
Method.textDocument_completion,
93+
CompletionRegistrationOptions(
94+
dartCompletionTriggerCharacters,
95+
null,
96+
true,
97+
[dartFiles],
98+
),
99+
);
100+
register(
101+
textCapabilities?.hover?.dynamicRegistration,
102+
Method.textDocument_hover,
103+
TextDocumentRegistrationOptions([dartFiles]),
104+
);
105+
register(
106+
textCapabilities?.signatureHelp?.dynamicRegistration,
107+
Method.textDocument_signatureHelp,
108+
SignatureHelpRegistrationOptions(
109+
dartSignatureHelpTriggerCharacters, [dartFiles]),
110+
);
111+
register(
112+
server.clientCapabilities?.textDocument?.references?.dynamicRegistration,
113+
Method.textDocument_references,
114+
TextDocumentRegistrationOptions([dartFiles]),
115+
);
116+
register(
117+
textCapabilities?.documentHighlight?.dynamicRegistration,
118+
Method.textDocument_documentHighlight,
119+
TextDocumentRegistrationOptions([dartFiles]),
120+
);
121+
register(
122+
textCapabilities?.documentSymbol?.dynamicRegistration,
123+
Method.textDocument_documentSymbol,
124+
TextDocumentRegistrationOptions([dartFiles]),
125+
);
126+
register(
127+
server.clientCapabilities?.textDocument?.formatting?.dynamicRegistration,
128+
Method.textDocument_formatting,
129+
TextDocumentRegistrationOptions([dartFiles]),
130+
);
131+
register(
132+
textCapabilities?.onTypeFormatting?.dynamicRegistration,
133+
Method.textDocument_onTypeFormatting,
134+
DocumentOnTypeFormattingRegistrationOptions(
135+
dartTypeFormattingCharacters.first,
136+
dartTypeFormattingCharacters.skip(1).toList(),
137+
[dartFiles],
138+
),
139+
);
140+
register(
141+
server.clientCapabilities?.textDocument?.definition?.dynamicRegistration,
142+
Method.textDocument_definition,
143+
TextDocumentRegistrationOptions([dartFiles]),
144+
);
145+
register(
146+
textCapabilities?.implementation?.dynamicRegistration,
147+
Method.textDocument_implementation,
148+
TextDocumentRegistrationOptions([dartFiles]),
149+
);
150+
register(
151+
server.clientCapabilities?.textDocument?.codeAction?.dynamicRegistration,
152+
Method.textDocument_codeAction,
153+
CodeActionRegistrationOptions(
154+
[dartFiles], DartCodeActionKind.serverSupportedKinds),
155+
);
156+
register(
157+
textCapabilities?.rename?.dynamicRegistration,
158+
Method.textDocument_rename,
159+
RenameRegistrationOptions(true, [dartFiles]),
160+
);
161+
register(
162+
textCapabilities?.foldingRange?.dynamicRegistration,
163+
Method.textDocument_foldingRange,
164+
TextDocumentRegistrationOptions([dartFiles]),
165+
);
166+
167+
// Only send the registration request if we have at least one (since
168+
// otherwise we don't know that the client supports registerCapability).
169+
if (registrations.isNotEmpty) {
170+
final registrationResponse = await server.sendRequest(
171+
Method.client_registerCapability,
172+
RegistrationParams(registrations),
173+
);
174+
175+
if (registrationResponse.error != null) {
176+
server.logErrorToClient(
177+
'Failed to register capabilities with client: '
178+
'(${registrationResponse.error.code}) '
179+
'${registrationResponse.error.message}',
180+
);
181+
}
182+
}
183+
}
39184
}

0 commit comments

Comments
 (0)