[analysis_server] Extract LSP registration options from ServerCapabilitiesComputer

This is a non-functional refactor that extracts the growing set of capabilities and options from ServerCapabilitiesComputer into files alongside the handlers they relate to.

The motivation for this is that for LSP-over-Legacy we'll need to accept client capabilities (and return server capabilities). The server capabilities will be different to the standard LSP ones (they will be a subset, and we might not support dynamic registration - at least initially). However the features we do support will have the same registration options, so to avoid duplicating them this moves the registration options away from the creation of the ServerCapabilities.

In future, we might consider further wrapping up a "feature" (which consists of these registration options, and the related handlers), but this change is already quite large and I just wanted to progress capabilities for LSP-over-Legacy so we can handle things like Code Actions (which require executeCommand and possible reverse-requests for applyEdit).

Change-Id: Iecd0aa36626fa44826f7d4dbd6e6c0d758075239
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/319840
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Danny Tuppeny 2023-08-10 17:10:48 +00:00 committed by Commit Queue
parent 94a0ce7757
commit acefe9ab6d
32 changed files with 1051 additions and 422 deletions

View file

@ -45,11 +45,48 @@ const dartSignatureHelpTriggerCharacters = <String>['('];
/// Characters to trigger formatting when format-on-type is enabled.
const dartTypeFormattingCharacters = ['}', ';'];
/// A [TextDocumentFilterWithScheme] for Analysis Options files.
final analysisOptionsFile = TextDocumentFilterWithScheme(
language: 'yaml', scheme: 'file', pattern: '**/analysis_options.yaml');
/// A [ProgressToken] used for reporting progress while the server is analyzing.
final analyzingProgressToken = ProgressToken.t2('ANALYZING');
/// A [TextDocumentFilterWithScheme] for Dart file.
final dartFiles =
TextDocumentFilterWithScheme(language: 'dart', scheme: 'file');
final emptyWorkspaceEdit = WorkspaceEdit();
final fileOperationRegistrationOptions = FileOperationRegistrationOptions(
filters: [
FileOperationFilter(
scheme: 'file',
pattern: FileOperationPattern(
glob: '**/*.dart',
matches: FileOperationPatternKind.file,
),
),
FileOperationFilter(
scheme: 'file',
pattern: FileOperationPattern(
glob: '**/',
matches: FileOperationPatternKind.folder,
),
)
],
);
/// A [TextDocumentFilterWithScheme] for Fix Data files.
final fixDataFile = TextDocumentFilterWithScheme(
language: 'yaml',
scheme: 'file',
pattern: '**/lib/{fix_data.yaml,fix_data/**.yaml}');
/// A [TextDocumentFilterWithScheme] for Pubspec files.
final pubspecFile = TextDocumentFilterWithScheme(
language: 'yaml', scheme: 'file', pattern: '**/pubspec.yaml');
/// Constants for command IDs that are exchanged between LSP client/server.
abstract class Commands {
/// A list of all commands IDs that can be sent to the client to inform which

View file

@ -9,13 +9,36 @@ import 'package:analysis_server/lsp_protocol/protocol_special.dart';
import 'package:analysis_server/src/analysis_server.dart';
import 'package:analysis_server/src/computer/computer_call_hierarchy.dart'
as call_hierarchy;
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/source/source_range.dart';
typedef StaticOptions
= Either3<bool, CallHierarchyOptions, CallHierarchyRegistrationOptions>;
class CallHierarchyRegistrations extends FeatureRegistration
with SingleDynamicRegistration, StaticRegistration<StaticOptions> {
CallHierarchyRegistrations(super.info);
@override
ToJsonable? get options =>
CallHierarchyRegistrationOptions(documentSelector: [dartFiles]);
@override
Method get registrationMethod => Method.textDocument_prepareCallHierarchy;
@override
StaticOptions get staticOptions => Either3.t1(true);
@override
bool get supportsDynamic => clientDynamic.callHierarchy;
}
/// A handler for `callHierarchy/incoming` that returns the incoming calls for
/// the target supplied by the client.
class IncomingCallHierarchyHandler extends _AbstractCallHierarchyCallsHandler<

View file

@ -4,13 +4,16 @@
import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
class WorkspaceFoldersHandler
typedef StaticOptions = Either2<bool, String>;
class ChangeWorkspaceFoldersHandler
extends LspMessageHandler<DidChangeWorkspaceFoldersParams, void> {
// Whether to update analysis roots based on the open workspace folders.
bool updateAnalysisRoots;
WorkspaceFoldersHandler(super.server)
ChangeWorkspaceFoldersHandler(super.server)
: updateAnalysisRoots =
!server.initializationOptions.onlyAnalyzeProjectsWithOpenFiles;
@ -46,3 +49,17 @@ class WorkspaceFoldersHandler
return folders.map((wf) => pathContext.fromUri(wf.uri)).toList();
}
}
class ChangeWorkspaceFoldersRegistrations extends FeatureRegistration
with StaticRegistration<StaticOptions> {
ChangeWorkspaceFoldersRegistrations(super.info);
@override
List<LspDynamicRegistration> get dynamicRegistrations => [];
@override
StaticOptions get staticOptions => Either2.t1(true);
@override
bool get supportsDynamic => false;
}

View file

@ -5,6 +5,7 @@
import 'dart:async';
import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/handlers/code_actions/abstract_code_actions_producer.dart';
import 'package:analysis_server/src/lsp/handlers/code_actions/analysis_options.dart';
import 'package:analysis_server/src/lsp/handlers/code_actions/dart.dart';
@ -12,9 +13,12 @@ import 'package:analysis_server/src/lsp/handlers/code_actions/plugins.dart';
import 'package:analysis_server/src/lsp/handlers/code_actions/pubspec.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analyzer/src/util/file_paths.dart' as file_paths;
import 'package:collection/collection.dart' show groupBy;
typedef StaticOptions = Either2<bool, CodeActionOptions>;
class CodeActionHandler
extends LspMessageHandler<CodeActionParams, TextDocumentCodeActionResult> {
CodeActionHandler(super.server);
@ -220,6 +224,36 @@ class CodeActionHandler
}
}
class CodeActionRegistrations extends FeatureRegistration
with SingleDynamicRegistration, StaticRegistration<StaticOptions> {
CodeActionRegistrations(super.info);
bool get codeActionLiteralSupport => clientCapabilities.literalCodeActions;
@override
ToJsonable? get options => CodeActionRegistrationOptions(
documentSelector: fullySupportedTypes,
codeActionKinds: DartCodeActionKind.serverSupportedKinds,
);
@override
Method get registrationMethod => Method.textDocument_codeAction;
@override
StaticOptions get staticOptions =>
// "The `CodeActionOptions` return type is only valid if the client
// signals code action literal support via the property
// `textDocument.codeAction.codeActionLiteralSupport`."
codeActionLiteralSupport
? Either2.t2(CodeActionOptions(
codeActionKinds: DartCodeActionKind.serverSupportedKinds,
))
: Either2.t1(true);
@override
bool get supportsDynamic => clientDynamic.codeActions;
}
/// Sorts [CodeActionWithPriority]s by priority, and removes duplicates keeping
/// the one nearest [range].
class _CodeActionSorter {

View file

@ -7,8 +7,10 @@ import 'dart:math' as math;
import 'package:analysis_server/lsp_protocol/protocol.dart' hide Declaration;
import 'package:analysis_server/src/computer/computer_hover.dart';
import 'package:analysis_server/src/lsp/client_capabilities.dart';
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analysis_server/src/provisional/completion/completion_core.dart';
import 'package:analysis_server/src/services/completion/completion_performance.dart';
import 'package:analysis_server/src/services/completion/dart/completion_manager.dart';
@ -790,6 +792,66 @@ class CompletionHandler
}
}
class CompletionRegistrations extends FeatureRegistration
with StaticRegistration<CompletionOptions> {
CompletionRegistrations(super.info);
@override
List<LspDynamicRegistration> get dynamicRegistrations {
return [
// Trigger and commit characters are specific to Dart, so register them
// separately to the others.
(
Method.textDocument_completion,
CompletionRegistrationOptions(
documentSelector: [dartFiles],
triggerCharacters: dartCompletionTriggerCharacters,
allCommitCharacters:
previewCommitCharacters ? dartCompletionCommitCharacters : null,
resolveProvider: true,
),
),
(
Method.textDocument_completion,
CompletionRegistrationOptions(
documentSelector: nonDartCompletionTypes,
resolveProvider: true,
),
),
];
}
/// Types of documents we support completion for that are not Dart.
///
/// We use two dynamic registrations because for Dart we support trigger
/// characters but for other kinds of files we do not.
List<TextDocumentFilterWithScheme> get nonDartCompletionTypes {
final pluginTypesExcludingDart =
pluginTypes.where((filter) => filter.pattern != '**/*.dart');
return {
...pluginTypesExcludingDart,
pubspecFile,
analysisOptionsFile,
fixDataFile,
}.toList();
}
bool get previewCommitCharacters =>
clientConfiguration.global.previewCommitCharacters;
@override
CompletionOptions get staticOptions => CompletionOptions(
triggerCharacters: dartCompletionTriggerCharacters,
allCommitCharacters:
previewCommitCharacters ? dartCompletionCommitCharacters : null,
resolveProvider: true,
);
@override
bool get supportsDynamic => clientDynamic.completion;
}
/// A set of completion items split into ranked and unranked items.
class _CompletionResults {
/// Items that can be ranked using their relevance/sortText.

View file

@ -7,6 +7,7 @@ import 'package:analysis_server/protocol/protocol_generated.dart'
hide AnalysisGetNavigationParams;
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analysis_server/src/plugin/result_merger.dart';
import 'package:analysis_server/src/protocol_server.dart' show NavigationTarget;
import 'package:analyzer/dart/analysis/results.dart';
@ -21,6 +22,8 @@ import 'package:analyzer_plugin/utilities/analyzer_converter.dart';
import 'package:analyzer_plugin/utilities/navigation/navigation_dart.dart';
import 'package:collection/collection.dart';
typedef StaticOptions = Either2<bool, DefinitionOptions>;
class DefinitionHandler extends LspMessageHandler<TextDocumentPositionParams,
TextDocumentDefinitionResult> with LspPluginRequestHandlerMixin {
DefinitionHandler(super.server);
@ -269,3 +272,21 @@ class DefinitionHandler extends LspMessageHandler<TextDocumentPositionParams,
return parsedLibrary.getElementDeclaration(element);
}
}
class DefinitionRegistrations extends FeatureRegistration
with SingleDynamicRegistration, StaticRegistration<StaticOptions> {
DefinitionRegistrations(super.info);
@override
ToJsonable? get options =>
TextDocumentRegistrationOptions(documentSelector: fullySupportedTypes);
@override
Method get registrationMethod => Method.textDocument_definition;
@override
StaticOptions get staticOptions => Either2.t1(true);
@override
bool get supportsDynamic => clientDynamic.definition;
}

View file

@ -5,10 +5,15 @@
import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/computer/computer_color.dart'
show ColorComputer, ColorReference;
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analyzer/dart/analysis/results.dart';
typedef StaticOptions
= Either3<bool, DocumentColorOptions, DocumentColorRegistrationOptions>;
/// Handles textDocument/documentColor requests.
///
/// This request is sent by the client to the server to request the locations
@ -58,3 +63,21 @@ class DocumentColorHandler
return success(colors.map(toColorInformation).toList());
}
}
class DocumentColorRegistrations extends FeatureRegistration
with SingleDynamicRegistration, StaticRegistration<StaticOptions> {
DocumentColorRegistrations(super.info);
@override
DocumentColorRegistrationOptions get options =>
DocumentColorRegistrationOptions(documentSelector: [dartFiles]);
@override
Method get registrationMethod => Method.textDocument_documentColor;
@override
StaticOptions get staticOptions => Either3.t3(options);
@override
bool get supportsDynamic => clientDynamic.colorProvider;
}

View file

@ -7,6 +7,9 @@ import 'package:analysis_server/src/domains/analysis/occurrences.dart';
import 'package:analysis_server/src/domains/analysis/occurrences_dart.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
typedef StaticOptions = Either2<bool, DocumentHighlightOptions>;
class DocumentHighlightsHandler extends SharedMessageHandler<
TextDocumentPositionParams, List<DocumentHighlight>?> {
@ -53,3 +56,21 @@ class DocumentHighlightsHandler extends SharedMessageHandler<
});
}
}
class DocumentHighlightsRegistrations extends FeatureRegistration
with SingleDynamicRegistration, StaticRegistration<StaticOptions> {
DocumentHighlightsRegistrations(super.info);
@override
ToJsonable? get options =>
TextDocumentRegistrationOptions(documentSelector: fullySupportedTypes);
@override
Method get registrationMethod => Method.textDocument_documentHighlight;
@override
StaticOptions get staticOptions => Either2.t1(true);
@override
bool get supportsDynamic => clientDynamic.documentHighlights;
}

View file

@ -7,10 +7,13 @@ import 'package:analysis_server/src/computer/computer_outline.dart';
import 'package:analysis_server/src/lsp/client_capabilities.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analysis_server/src/protocol_server.dart' show Outline;
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/source/line_info.dart';
typedef StaticOptions = Either2<bool, DocumentSymbolOptions>;
class DocumentSymbolHandler extends SharedMessageHandler<DocumentSymbolParams,
TextDocumentDocumentSymbolResult> {
DocumentSymbolHandler(super.server);
@ -138,3 +141,21 @@ class DocumentSymbolHandler extends SharedMessageHandler<DocumentSymbolParams,
}
}
}
class DocumentSymbolsRegistrations extends FeatureRegistration
with SingleDynamicRegistration, StaticRegistration<StaticOptions> {
DocumentSymbolsRegistrations(super.info);
@override
ToJsonable? get options =>
TextDocumentRegistrationOptions(documentSelector: fullySupportedTypes);
@override
Method get registrationMethod => Method.textDocument_documentSymbol;
@override
StaticOptions get staticOptions => Either2.t1(true);
@override
bool get supportsDynamic => clientDynamic.documentSymbol;
}

View file

@ -14,6 +14,7 @@ import 'package:analysis_server/src/lsp/handlers/commands/sort_members.dart';
import 'package:analysis_server/src/lsp/handlers/commands/validate_refactor.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/progress.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analysis_server/src/services/refactoring/framework/refactoring_processor.dart';
/// Handles workspace/executeCommand messages by delegating to a specific
@ -84,3 +85,20 @@ class ExecuteCommandHandler
return handler.handle(message, commandParams, progress, token);
}
}
class ExecuteCommandRegistrations extends FeatureRegistration
with StaticRegistration<ExecuteCommandOptions> {
ExecuteCommandRegistrations(super.info);
@override
List<LspDynamicRegistration> get dynamicRegistrations => [];
@override
ExecuteCommandOptions get staticOptions => ExecuteCommandOptions(
commands: Commands.serverSupportedCommands,
workDoneProgress: true,
);
@override
bool get supportsDynamic => false;
}

View file

@ -6,9 +6,13 @@ import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/computer/computer_folding.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analysis_server/src/protocol_server.dart';
import 'package:analyzer/source/line_info.dart';
typedef StaticOptions
= Either3<bool, FoldingRangeOptions, FoldingRangeRegistrationOptions>;
class FoldingHandler
extends LspMessageHandler<FoldingRangeParams, List<FoldingRange>> {
FoldingHandler(super.server);
@ -128,3 +132,21 @@ class FoldingHandler
);
}
}
class FoldingRegistrations extends FeatureRegistration
with SingleDynamicRegistration, StaticRegistration<StaticOptions> {
FoldingRegistrations(super.info);
@override
ToJsonable? get options =>
TextDocumentRegistrationOptions(documentSelector: fullySupportedTypes);
@override
Method get registrationMethod => Method.textDocument_foldingRange;
@override
StaticOptions get staticOptions => Either3.t1(true);
@override
bool get supportsDynamic => clientDynamic.folding;
}

View file

@ -6,8 +6,11 @@ import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analysis_server/src/lsp/source_edits.dart';
typedef StaticOptions = DocumentOnTypeFormattingOptions?;
class FormatOnTypeHandler extends SharedMessageHandler<
DocumentOnTypeFormattingParams, List<TextEdit>?> {
FormatOnTypeHandler(super.server);
@ -54,3 +57,35 @@ class FormatOnTypeHandler extends SharedMessageHandler<
});
}
}
class FormatOnTypeRegistrations extends FeatureRegistration
with SingleDynamicRegistration, StaticRegistration<StaticOptions> {
FormatOnTypeRegistrations(super.info);
bool get enableFormatter => clientConfiguration.global.enableSdkFormatter;
@override
ToJsonable? get options {
return DocumentOnTypeFormattingRegistrationOptions(
documentSelector: [dartFiles], // This is currently Dart-specific
firstTriggerCharacter: dartTypeFormattingCharacters.first,
moreTriggerCharacter: dartTypeFormattingCharacters.skip(1).toList(),
);
}
@override
Method get registrationMethod => Method.textDocument_onTypeFormatting;
@override
StaticOptions get staticOptions => enableFormatter
? DocumentOnTypeFormattingOptions(
firstTriggerCharacter: dartTypeFormattingCharacters.first,
moreTriggerCharacter: dartTypeFormattingCharacters.skip(1).toList())
: null;
@override
bool get supportsDynamic => enableFormatter && clientDynamic.typeFormatting;
@override
bool get supportsStatic => enableFormatter;
}

View file

@ -6,8 +6,11 @@ import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analysis_server/src/lsp/source_edits.dart';
typedef StaticOptions = Either2<bool, DocumentRangeFormattingOptions>;
class FormatRangeHandler extends SharedMessageHandler<
DocumentRangeFormattingParams, List<TextEdit>?> {
FormatRangeHandler(super.server);
@ -53,3 +56,27 @@ class FormatRangeHandler extends SharedMessageHandler<
});
}
}
class FormatRangeRegistrations extends FeatureRegistration
with SingleDynamicRegistration, StaticRegistration<StaticOptions> {
FormatRangeRegistrations(super.info);
bool get enableFormatter => clientConfiguration.global.enableSdkFormatter;
@override
ToJsonable? get options => DocumentRangeFormattingRegistrationOptions(
documentSelector: [dartFiles], // This is currently Dart-specific
);
@override
Method get registrationMethod => Method.textDocument_rangeFormatting;
@override
StaticOptions get staticOptions => Either2.t1(true);
@override
bool get supportsDynamic => enableFormatter && clientDynamic.rangeFormatting;
@override
bool get supportsStatic => enableFormatter;
}

View file

@ -6,8 +6,11 @@ import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analysis_server/src/lsp/source_edits.dart';
typedef StaticOptions = Either2<bool, DocumentFormattingOptions>;
class FormattingHandler
extends SharedMessageHandler<DocumentFormattingParams, List<TextEdit>?> {
FormattingHandler(super.server);
@ -53,3 +56,26 @@ class FormattingHandler
});
}
}
class FormattingRegistrations extends FeatureRegistration
with SingleDynamicRegistration, StaticRegistration<StaticOptions> {
FormattingRegistrations(super.info);
bool get enableFormatter => clientConfiguration.global.enableSdkFormatter;
@override
ToJsonable? get options =>
TextDocumentRegistrationOptions(documentSelector: fullySupportedTypes);
@override
Method get registrationMethod => Method.textDocument_formatting;
@override
StaticOptions get staticOptions => Either2.t1(true);
@override
bool get supportsDynamic => enableFormatter && clientDynamic.formatting;
@override
bool get supportsStatic => enableFormatter;
}

View file

@ -8,9 +8,12 @@ import 'package:analysis_server/src/computer/computer_hover.dart';
import 'package:analysis_server/src/lsp/dartdoc.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/source/line_info.dart';
typedef StaticOptions = Either2<bool, HoverOptions>;
class HoverHandler
extends SharedMessageHandler<TextDocumentPositionParams, Hover?> {
HoverHandler(super.server);
@ -105,3 +108,21 @@ class HoverHandler
return success(toHover(unit.lineInfo, hover));
}
}
class HoverRegistrations extends FeatureRegistration
with SingleDynamicRegistration, StaticRegistration<StaticOptions> {
HoverRegistrations(super.info);
@override
ToJsonable? get options =>
TextDocumentRegistrationOptions(documentSelector: fullySupportedTypes);
@override
Method get registrationMethod => Method.textDocument_hover;
@override
StaticOptions get staticOptions => Either2.t1(true);
@override
bool get supportsDynamic => clientDynamic.hover;
}

View file

@ -6,6 +6,7 @@ import 'package:analysis_server/lsp_protocol/protocol.dart'
hide TypeHierarchyItem, Element;
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analysis_server/src/search/type_hierarchy.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/element/element.dart';
@ -13,6 +14,9 @@ import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer/src/util/performance/operation_performance.dart';
import 'package:collection/collection.dart';
typedef StaticOptions
= Either3<bool, ImplementationOptions, ImplementationRegistrationOptions>;
class ImplementationHandler
extends SharedMessageHandler<TextDocumentPositionParams, List<Location>> {
ImplementationHandler(super.server);
@ -101,3 +105,21 @@ class ImplementationHandler
return success(locations);
}
}
class ImplementationRegistrations extends FeatureRegistration
with SingleDynamicRegistration, StaticRegistration<StaticOptions> {
ImplementationRegistrations(super.info);
@override
ToJsonable? get options =>
TextDocumentRegistrationOptions(documentSelector: fullySupportedTypes);
@override
Method get registrationMethod => Method.textDocument_implementation;
@override
StaticOptions get staticOptions => Either3.t1(true);
@override
bool get supportsDynamic => clientDynamic.implementation;
}

View file

@ -4,10 +4,15 @@
import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/computer/computer_inlay_hint.dart';
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analyzer/dart/analysis/results.dart';
typedef StaticOptions
= Either3<bool, InlayHintOptions, InlayHintRegistrationOptions>;
class InlayHintHandler
extends LspMessageHandler<InlayHintParams, List<InlayHint>> {
InlayHintHandler(super.server);
@ -52,3 +57,25 @@ class InlayHintHandler
});
}
}
class InlayHintRegistrations extends FeatureRegistration
with SingleDynamicRegistration, StaticRegistration<StaticOptions> {
InlayHintRegistrations(super.info);
@override
ToJsonable? get options => InlayHintRegistrationOptions(
documentSelector: [dartFiles],
resolveProvider: false,
);
@override
Method get registrationMethod => Method.textDocument_inlayHint;
@override
StaticOptions get staticOptions => Either3.t2(
InlayHintOptions(resolveProvider: false),
);
@override
bool get supportsDynamic => clientDynamic.inlayHints;
}

View file

@ -5,6 +5,7 @@
import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analysis_server/src/protocol_server.dart' show NavigationTarget;
import 'package:analysis_server/src/search/element_references.dart';
import 'package:analysis_server/src/services/search/search_engine.dart'
@ -17,6 +18,8 @@ import 'package:analyzer_plugin/src/utilities/navigation/navigation.dart';
import 'package:analyzer_plugin/utilities/navigation/navigation_dart.dart';
import 'package:collection/collection.dart';
typedef StaticOptions = Either2<bool, ReferenceOptions>;
class ReferencesHandler
extends LspMessageHandler<ReferenceParams, List<Location>?> {
ReferencesHandler(super.server);
@ -126,3 +129,21 @@ class ReferencesHandler
return node;
}
}
class ReferencesRegistrations extends FeatureRegistration
with SingleDynamicRegistration, StaticRegistration<StaticOptions> {
ReferencesRegistrations(super.info);
@override
ToJsonable? get options =>
TextDocumentRegistrationOptions(documentSelector: fullySupportedTypes);
@override
Method get registrationMethod => Method.textDocument_references;
@override
StaticOptions get staticOptions => Either2.t1(true);
@override
bool get supportsDynamic => clientDynamic.references;
}

View file

@ -8,12 +8,15 @@ import 'package:analysis_server/src/lsp/client_configuration.dart';
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analysis_server/src/services/refactoring/legacy/refactoring.dart';
import 'package:analysis_server/src/services/refactoring/legacy/rename_unit_member.dart';
import 'package:analysis_server/src/utilities/extensions/string.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
typedef StaticOptions = Either2<bool, RenameOptions>;
class PrepareRenameHandler extends LspMessageHandler<TextDocumentPositionParams,
TextDocumentPrepareRenameResult> {
PrepareRenameHandler(super.server);
@ -282,3 +285,23 @@ class RenameHandler extends LspMessageHandler<RenameParams, WorkspaceEdit?>
return userChoice == UserPromptActions.yes;
}
}
class RenameRegistrations extends FeatureRegistration
with SingleDynamicRegistration, StaticRegistration<StaticOptions> {
RenameRegistrations(super.info);
@override
ToJsonable? get options => RenameRegistrationOptions(
documentSelector: fullySupportedTypes, prepareProvider: true);
@override
Method get registrationMethod => Method.textDocument_rename;
@override
StaticOptions get staticOptions => clientCapabilities.renameValidation
? Either2<bool, RenameOptions>.t2(RenameOptions(prepareProvider: true))
: Either2<bool, RenameOptions>.t1(true);
@override
bool get supportsDynamic => clientDynamic.rename;
}

View file

@ -5,11 +5,16 @@
import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/computer/computer_selection_ranges.dart'
hide SelectionRange;
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
typedef StaticOptions
= Either3<bool, SelectionRangeOptions, SelectionRangeRegistrationOptions>;
class SelectionRangeHandler
extends LspMessageHandler<SelectionRangeParams, List<SelectionRange>?> {
SelectionRangeHandler(super.server);
@ -72,3 +77,21 @@ class SelectionRangeHandler
.toList();
}
}
class SelectionRangeRegistrations extends FeatureRegistration
with SingleDynamicRegistration, StaticRegistration<StaticOptions> {
SelectionRangeRegistrations(super.info);
@override
ToJsonable? get options =>
SelectionRangeRegistrationOptions(documentSelector: [dartFiles]);
@override
Method get registrationMethod => Method.textDocument_selectionRange;
@override
StaticOptions get staticOptions => Either3.t1(true);
@override
bool get supportsDynamic => clientDynamic.selectionRange;
}

View file

@ -6,12 +6,18 @@ import 'dart:async';
import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/computer/computer_highlights.dart';
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analysis_server/src/lsp/semantic_tokens/encoder.dart';
import 'package:analysis_server/src/lsp/semantic_tokens/legend.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
typedef StaticOptions
= Either2<SemanticTokensOptions, SemanticTokensRegistrationOptions>;
abstract class AbstractSemanticTokensHandler<T>
extends LspMessageHandler<T, SemanticTokens?>
with LspPluginRequestHandlerMixin {
@ -142,3 +148,34 @@ class SemanticTokensRangeHandler
MessageInfo message, CancellationToken token) =>
_handleImpl(params.textDocument, token, range: params.range);
}
class SemanticTokensRegistrations extends FeatureRegistration
with SingleDynamicRegistration, StaticRegistration<StaticOptions> {
SemanticTokensRegistrations(super.info);
@override
ToJsonable? get options => SemanticTokensRegistrationOptions(
documentSelector: fullySupportedTypes,
legend: semanticTokenLegend.lspLegend,
full: Either2<bool, SemanticTokensOptionsFull>.t2(
SemanticTokensOptionsFull(delta: false),
),
range: Either2<bool, SemanticTokensOptionsRange>.t1(true),
);
@override
Method get registrationMethod =>
CustomMethods.semanticTokenDynamicRegistration;
@override
StaticOptions get staticOptions => Either2.t1(
SemanticTokensOptions(
legend: semanticTokenLegend.lspLegend,
full: Either2.t2(SemanticTokensOptionsFull(delta: false)),
range: Either2.t1(true),
),
);
@override
bool get supportsDynamic => clientDynamic.semanticTokens;
}

View file

@ -5,8 +5,10 @@
import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/computer/computer_signature.dart';
import 'package:analysis_server/src/computer/computer_type_arguments_signature.dart';
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/src/dartdoc/dartdoc_directive_info.dart';
@ -129,3 +131,27 @@ class SignatureHelpHandler
return typeSignature;
}
}
class SignatureHelpRegistrations extends FeatureRegistration
with SingleDynamicRegistration, StaticRegistration<SignatureHelpOptions> {
SignatureHelpRegistrations(super.info);
@override
ToJsonable? get options => SignatureHelpRegistrationOptions(
documentSelector: fullySupportedTypes,
triggerCharacters: dartSignatureHelpTriggerCharacters,
retriggerCharacters: dartSignatureHelpRetriggerCharacters,
);
@override
Method get registrationMethod => Method.textDocument_signatureHelp;
@override
SignatureHelpOptions get staticOptions => SignatureHelpOptions(
triggerCharacters: dartSignatureHelpTriggerCharacters,
retriggerCharacters: dartSignatureHelpRetriggerCharacters,
);
@override
bool get supportsDynamic => clientDynamic.signatureHelp;
}

View file

@ -80,7 +80,7 @@ class InitializedLspStateMessageHandler extends InitializedStateMessageHandler {
ReferencesHandler.new,
CodeActionHandler.new,
ExecuteCommandHandler.new,
WorkspaceFoldersHandler.new,
ChangeWorkspaceFoldersHandler.new,
PrepareRenameHandler.new,
RenameHandler.new,
FoldingHandler.new,

View file

@ -7,8 +7,11 @@ import 'dart:async';
import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analysis_server/src/lsp/source_edits.dart';
typedef StaticOptions = Either2<TextDocumentSyncKind, TextDocumentSyncOptions>;
class TextDocumentChangeHandler
extends LspMessageHandler<DidChangeTextDocumentParams, void> {
TextDocumentChangeHandler(super.server);
@ -108,3 +111,49 @@ class TextDocumentOpenHandler
});
}
}
class TextDocumentRegistrations extends FeatureRegistration
with StaticRegistration<StaticOptions> {
TextDocumentRegistrations(super.info);
@override
List<LspDynamicRegistration> get dynamicRegistrations {
return [
(
Method.textDocument_didOpen,
TextDocumentRegistrationOptions(documentSelector: synchronisedTypes),
),
(
Method.textDocument_didClose,
TextDocumentRegistrationOptions(documentSelector: synchronisedTypes),
),
(
Method.textDocument_didChange,
TextDocumentChangeRegistrationOptions(
syncKind: TextDocumentSyncKind.Incremental,
documentSelector: synchronisedTypes),
)
];
}
@override
StaticOptions get staticOptions => Either2.t2(TextDocumentSyncOptions(
openClose: true,
change: TextDocumentSyncKind.Incremental,
willSave: false,
willSaveWaitUntil: false,
save: null,
));
@override
bool get supportsDynamic => clientDynamic.textSync;
List<TextDocumentFilterWithScheme> get synchronisedTypes {
return {
...fullySupportedTypes,
pubspecFile,
analysisOptionsFile,
fixDataFile,
}.toList();
}
}

View file

@ -3,8 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/syntactic_entity.dart';
import 'package:analyzer/dart/element/element.dart';
@ -17,6 +19,9 @@ import 'package:analyzer/src/dart/element/element.dart' as analyzer;
import 'package:analyzer_plugin/protocol/protocol_common.dart' as plugin;
import 'package:analyzer_plugin/utilities/analyzer_converter.dart';
typedef StaticOptions
= Either3<bool, TypeDefinitionOptions, TypeDefinitionRegistrationOptions>;
class TypeDefinitionHandler extends SharedMessageHandler<TypeDefinitionParams,
TextDocumentTypeDefinitionResult> with LspPluginRequestHandlerMixin {
static const _emptyResult = TextDocumentTypeDefinitionResult.t2([]);
@ -188,3 +193,22 @@ class TypeDefinitionHandler extends SharedMessageHandler<TypeDefinitionParams,
return node.staticType;
}
}
class TypeDefinitionRegistrations extends FeatureRegistration
with SingleDynamicRegistration, StaticRegistration<StaticOptions> {
TypeDefinitionRegistrations(super.info);
@override
ToJsonable? get options => TextDocumentRegistrationOptions(
documentSelector: [dartFiles], // This is currently Dart-specific
);
@override
Method get registrationMethod => Method.textDocument_typeDefinition;
@override
StaticOptions get staticOptions => Either3.t1(true);
@override
bool get supportsDynamic => clientDynamic.typeDefinition;
}

View file

@ -8,14 +8,19 @@ import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/analysis_server.dart';
import 'package:analysis_server/src/computer/computer_lazy_type_hierarchy.dart'
as type_hierarchy;
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:analyzer/src/dart/element/element.dart';
typedef StaticOptions
= Either3<bool, TypeHierarchyOptions, TypeHierarchyRegistrationOptions>;
/// A handler for the initial "prepare" request for starting navigation with
/// Type Hierarchy.
///
@ -70,6 +75,25 @@ class PrepareTypeHierarchyHandler extends SharedMessageHandler<
}
}
class TypeHierarchyRegistrations extends FeatureRegistration
with SingleDynamicRegistration, StaticRegistration<StaticOptions> {
TypeHierarchyRegistrations(super.info);
@override
ToJsonable? get options => TypeHierarchyRegistrationOptions(
documentSelector: [dartFiles],
);
@override
Method get registrationMethod => Method.textDocument_prepareTypeHierarchy;
@override
StaticOptions get staticOptions => Either3.t1(true);
@override
bool get supportsDynamic => clientDynamic.typeHierarchy;
}
class TypeHierarchySubtypesHandler extends SharedMessageHandler<
TypeHierarchySubtypesParams,
TypeHierarchySubtypesResult> with _TypeHierarchyUtils {

View file

@ -3,10 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analysis_server/src/services/refactoring/legacy/refactoring.dart';
typedef StaticOptions = FileOperationRegistrationOptions?;
class WillRenameFilesHandler
extends LspMessageHandler<RenameFilesParams, WorkspaceEdit?> {
WillRenameFilesHandler(super.server);
@ -56,3 +60,28 @@ class WillRenameFilesHandler
return success(edit);
}
}
class WillRenameFilesRegistrations extends FeatureRegistration
with SingleDynamicRegistration, StaticRegistration<StaticOptions> {
WillRenameFilesRegistrations(super.info);
@override
FileOperationRegistrationOptions? get options =>
fileOperationRegistrationOptions;
@override
Method get registrationMethod => Method.workspace_willRenameFiles;
@override
StaticOptions get staticOptions => options;
@override
bool get supportsDynamic =>
updateImportsOnRename && clientDynamic.fileOperations;
@override
bool get supportsStatic => updateImportsOnRename;
bool get updateImportsOnRename =>
clientConfiguration.global.updateImportsOnRename;
}

View file

@ -4,6 +4,7 @@
import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
class WorkspaceDidChangeConfigurationMessageHandler
extends LspMessageHandler<DidChangeConfigurationParams, void> {
@ -28,3 +29,17 @@ class WorkspaceDidChangeConfigurationMessageHandler
return success(null);
}
}
class WorkspaceDidChangeConfigurationRegistrations extends FeatureRegistration
with SingleDynamicRegistration {
WorkspaceDidChangeConfigurationRegistrations(super.info);
@override
ToJsonable? get options => null;
@override
Method get registrationMethod => Method.workspace_didChangeConfiguration;
@override
bool get supportsDynamic => clientDynamic.didChangeConfiguration;
}

View file

@ -5,8 +5,11 @@
import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analyzer/src/dart/analysis/search.dart' as search;
typedef StaticOptions = Either2<bool, WorkspaceSymbolOptions>;
class WorkspaceSymbolHandler extends SharedMessageHandler<WorkspaceSymbolParams,
List<SymbolInformation>> {
WorkspaceSymbolHandler(super.server);
@ -115,3 +118,17 @@ class WorkspaceSymbolHandler extends SharedMessageHandler<WorkspaceSymbolParams,
containerName: declaration.className ?? declaration.mixinName);
}
}
class WorkspaceSymbolRegistrations extends FeatureRegistration
with StaticRegistration<StaticOptions> {
WorkspaceSymbolRegistrations(super.info);
@override
List<LspDynamicRegistration> get dynamicRegistrations => [];
@override
StaticOptions get staticOptions => Either2.t1(true);
@override
bool get supportsDynamic => false;
}

View file

@ -0,0 +1,228 @@
// 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 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/lsp/client_capabilities.dart';
import 'package:analysis_server/src/lsp/client_configuration.dart';
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/handlers/handler_call_hierarchy.dart';
import 'package:analysis_server/src/lsp/handlers/handler_change_workspace_folders.dart';
import 'package:analysis_server/src/lsp/handlers/handler_code_actions.dart';
import 'package:analysis_server/src/lsp/handlers/handler_completion.dart';
import 'package:analysis_server/src/lsp/handlers/handler_definition.dart';
import 'package:analysis_server/src/lsp/handlers/handler_document_color.dart';
import 'package:analysis_server/src/lsp/handlers/handler_document_highlights.dart';
import 'package:analysis_server/src/lsp/handlers/handler_document_symbols.dart';
import 'package:analysis_server/src/lsp/handlers/handler_execute_command.dart';
import 'package:analysis_server/src/lsp/handlers/handler_folding.dart';
import 'package:analysis_server/src/lsp/handlers/handler_format_on_type.dart';
import 'package:analysis_server/src/lsp/handlers/handler_format_range.dart';
import 'package:analysis_server/src/lsp/handlers/handler_formatting.dart';
import 'package:analysis_server/src/lsp/handlers/handler_hover.dart';
import 'package:analysis_server/src/lsp/handlers/handler_implementation.dart';
import 'package:analysis_server/src/lsp/handlers/handler_inlay_hint.dart';
import 'package:analysis_server/src/lsp/handlers/handler_references.dart';
import 'package:analysis_server/src/lsp/handlers/handler_rename.dart';
import 'package:analysis_server/src/lsp/handlers/handler_selection_range.dart';
import 'package:analysis_server/src/lsp/handlers/handler_semantic_tokens.dart';
import 'package:analysis_server/src/lsp/handlers/handler_signature_help.dart';
import 'package:analysis_server/src/lsp/handlers/handler_text_document_changes.dart';
import 'package:analysis_server/src/lsp/handlers/handler_type_definition.dart';
import 'package:analysis_server/src/lsp/handlers/handler_type_hierarchy.dart';
import 'package:analysis_server/src/lsp/handlers/handler_will_rename_files.dart';
import 'package:analysis_server/src/lsp/handlers/handler_workspace_configuration.dart';
import 'package:analysis_server/src/lsp/handlers/handler_workspace_symbols.dart';
import 'package:analysis_server/src/lsp/server_capabilities_computer.dart';
typedef LspDynamicRegistration = (Method, ToJsonable?);
/// Provides static/dynamic registration info for an LSP feature.
abstract class FeatureRegistration {
final RegistrationContext _context;
FeatureRegistration(this._context);
/// The capabilities of the client.
LspClientCapabilities get clientCapabilities => _context.clientCapabilities;
/// The configuration provided by the client.
LspClientConfiguration get clientConfiguration =>
_context.clientConfiguration;
/// A helper to see which features the client supports dynamic registrations
/// for. This information is derived from the [ClientCapabilities].
ClientDynamicRegistrations get clientDynamic => _context.clientDynamic;
/// Gets all dynamic registrations for this feature.
///
/// These registrations should only be used if [supportsDynamic] returns true.
List<LspDynamicRegistration> get dynamicRegistrations;
/// Types of documents that are fully supported by the server.
///
/// File types like pubspec.yaml, analysis_options.yaml and fix_data files are
/// not included here as their support is very limited and do not provide
/// functionality in most handlers.
List<TextDocumentFilterWithScheme> get fullySupportedTypes {
return {
dartFiles,
...pluginTypes,
}.toList();
}
/// Types of documents that loaded plugins are interetsed in.
List<TextDocumentFilterWithScheme> get pluginTypes => _context.pluginTypes;
/// Whether both the client, and this feature, support dynamic registration.
bool get supportsDynamic;
}
/// A helper to provide access to all feature registrations.
class LspFeatures {
final CallHierarchyRegistrations callHierarchy;
final ChangeWorkspaceFoldersRegistrations changeNotifications;
final CodeActionRegistrations codeActions;
final CompletionRegistrations completion;
final DefinitionRegistrations definition;
final DocumentColorRegistrations colors;
final DocumentHighlightsRegistrations documentHighlight;
final DocumentSymbolsRegistrations documentSymbol;
final ExecuteCommandRegistrations executeCommand;
final FoldingRegistrations foldingRange;
final FormatOnTypeRegistrations formatOnType;
final FormatRangeRegistrations formatRange;
final FormattingRegistrations format;
final HoverRegistrations hover;
final ImplementationRegistrations implementation;
final InlayHintRegistrations inlayHint;
final ReferencesRegistrations references;
final RenameRegistrations rename;
final SelectionRangeRegistrations selectionRange;
final SemanticTokensRegistrations semanticTokens;
final SignatureHelpRegistrations signatureHelp;
final TextDocumentRegistrations textDocumentSync;
final TypeDefinitionRegistrations typeDefinition;
final TypeHierarchyRegistrations typeHierarchy;
final WillRenameFilesRegistrations willRename;
final WorkspaceDidChangeConfigurationRegistrations
workspaceDidChangeConfiguration;
final WorkspaceSymbolRegistrations workspaceSymbol;
LspFeatures(RegistrationContext context)
: callHierarchy = CallHierarchyRegistrations(context),
changeNotifications = ChangeWorkspaceFoldersRegistrations(context),
codeActions = CodeActionRegistrations(context),
colors = DocumentColorRegistrations(context),
completion = CompletionRegistrations(context),
definition = DefinitionRegistrations(context),
format = FormattingRegistrations(context),
documentHighlight = DocumentHighlightsRegistrations(context),
formatOnType = FormatOnTypeRegistrations(context),
formatRange = FormatRangeRegistrations(context),
documentSymbol = DocumentSymbolsRegistrations(context),
executeCommand = ExecuteCommandRegistrations(context),
foldingRange = FoldingRegistrations(context),
hover = HoverRegistrations(context),
implementation = ImplementationRegistrations(context),
inlayHint = InlayHintRegistrations(context),
references = ReferencesRegistrations(context),
rename = RenameRegistrations(context),
selectionRange = SelectionRangeRegistrations(context),
semanticTokens = SemanticTokensRegistrations(context),
signatureHelp = SignatureHelpRegistrations(context),
textDocumentSync = TextDocumentRegistrations(context),
typeDefinition = TypeDefinitionRegistrations(context),
typeHierarchy = TypeHierarchyRegistrations(context),
willRename = WillRenameFilesRegistrations(context),
workspaceDidChangeConfiguration =
WorkspaceDidChangeConfigurationRegistrations(context),
workspaceSymbol = WorkspaceSymbolRegistrations(context);
List<FeatureRegistration> get allFeatures => [
callHierarchy,
changeNotifications,
codeActions,
completion,
definition,
colors,
documentHighlight,
documentSymbol,
executeCommand,
foldingRange,
formatOnType,
formatRange,
format,
hover,
implementation,
inlayHint,
references,
rename,
selectionRange,
semanticTokens,
signatureHelp,
textDocumentSync,
typeDefinition,
typeHierarchy,
willRename,
workspaceDidChangeConfiguration,
workspaceSymbol,
];
}
class RegistrationContext {
/// A helper to see which features the client supports dynamic registrations
/// for. This information is derived from the [ClientCapabilities].
final ClientDynamicRegistrations clientDynamic;
/// Types of documents that loaded plugins are interetsed in.
final List<TextDocumentFilterWithScheme> pluginTypes;
/// The capabilities of the client.
final LspClientCapabilities clientCapabilities;
/// The configuration provided by the client.
final LspClientConfiguration clientConfiguration;
RegistrationContext({
required this.clientCapabilities,
required this.clientConfiguration,
required this.pluginTypes,
}) : clientDynamic = ClientDynamicRegistrations(clientCapabilities.raw);
}
/// A helper mixin to simplify feature registrations that only provide a single
/// dynamic registration.
mixin SingleDynamicRegistration on FeatureRegistration {
@override
List<LspDynamicRegistration> get dynamicRegistrations {
return [(registrationMethod, options)];
}
/// The options to use for static registration if it is to be used.
ToJsonable? get options;
/// The [Method] used for dynamic registration.
Method get registrationMethod;
}
/// A helper that adds support for static registration of a feature.
mixin StaticRegistration<T> on FeatureRegistration {
/// The raw options used for static registration. This should be accessed via
/// [staticRegistration] to ensure it's only used when a) static registration
/// is supported/enabled and b) dynamic registration is not supported.
T get staticOptions;
/// Only return static registration options if we support static and do not
/// support dynamic registration.
///
/// Some features will override [supportsStatic] to check options, so we must
/// check [supportsDynamic] explicitly too.
T? get staticRegistration =>
supportsStatic && !supportsDynamic ? staticOptions : null;
/// Whether this feature supports static registration.
///
/// This is usually `true`, but may be overridden by client settings.
bool get supportsStatic => true;
}

View file

@ -7,7 +7,7 @@ import 'package:analysis_server/src/analysis_server.dart';
import 'package:analysis_server/src/lsp/client_capabilities.dart';
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/lsp_analysis_server.dart';
import 'package:analysis_server/src/lsp/semantic_tokens/legend.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
/// Helper for reading client dynamic registrations which may be omitted by the
/// client.
@ -131,197 +131,74 @@ class ClientDynamicRegistrations {
}
class ServerCapabilitiesComputer {
static final fileOperationRegistrationOptions =
FileOperationRegistrationOptions(
filters: [
FileOperationFilter(
scheme: 'file',
pattern: FileOperationPattern(
glob: '**/*.dart',
matches: FileOperationPatternKind.file,
),
),
FileOperationFilter(
scheme: 'file',
pattern: FileOperationPattern(
glob: '**/',
matches: FileOperationPatternKind.folder,
),
)
],
);
final LspAnalysisServer _server;
/// List of current registrations.
Set<Registration> currentRegistrations = {};
var _lastRegistrationId = 0;
final dartFiles =
TextDocumentFilterWithScheme(language: 'dart', scheme: 'file');
final pubspecFile = TextDocumentFilterWithScheme(
language: 'yaml', scheme: 'file', pattern: '**/pubspec.yaml');
final analysisOptionsFile = TextDocumentFilterWithScheme(
language: 'yaml', scheme: 'file', pattern: '**/analysis_options.yaml');
final fixDataFile = TextDocumentFilterWithScheme(
language: 'yaml',
scheme: 'file',
pattern: '**/lib/{fix_data.yaml,fix_data/**.yaml}');
ServerCapabilitiesComputer(this._server);
List<TextDocumentFilterWithScheme> get pluginTypes => AnalysisServer
.supportsPlugins
? _server.pluginManager.plugins
.expand(
(plugin) => plugin.currentSession?.interestingFiles ?? const [],
)
// All published plugins use something like `*.extension` as
// interestingFiles. Prefix a `**/` so that the glob matches nested
// folders as well.
.map((glob) =>
TextDocumentFilterWithScheme(scheme: 'file', pattern: '**/$glob'))
.toList()
: <TextDocumentFilterWithScheme>[];
ServerCapabilities computeServerCapabilities(
LspClientCapabilities clientCapabilities) {
final codeActionLiteralSupport = clientCapabilities.literalCodeActions;
final renameOptionsSupport = clientCapabilities.renameValidation;
final enableFormatter =
_server.lspClientConfiguration.global.enableSdkFormatter;
final previewCommitCharacters =
_server.lspClientConfiguration.global.previewCommitCharacters;
LspClientCapabilities clientCapabilities,
) {
final context = RegistrationContext(
clientCapabilities: clientCapabilities,
clientConfiguration: _server.lspClientConfiguration,
pluginTypes: pluginTypes,
);
final features = LspFeatures(context);
final dynamicRegistrations =
ClientDynamicRegistrations(clientCapabilities.raw);
// When adding new capabilities to the server that may apply to specific file
// types, it's important to update
// [InitializedMessageHandler._performDynamicRegistration()] to notify
// supporting clients of this. This avoids clients needing to hard-code the
// list of what files types we support (and allows them to avoid sending
// requests where we have only partial support for some types).
return ServerCapabilities(
textDocumentSync: dynamicRegistrations.textSync
? null
: Either2<TextDocumentSyncKind, TextDocumentSyncOptions>.t2(
TextDocumentSyncOptions(
// The open/close and sync kind flags are registered dynamically if the
// client supports them, so these static registrations are based on whether
// the client supports dynamic registration.
openClose: true,
change: TextDocumentSyncKind.Incremental,
willSave: false,
willSaveWaitUntil: false,
save: null,
)),
callHierarchyProvider: dynamicRegistrations.callHierarchy
? null
: Either3<bool, CallHierarchyOptions,
CallHierarchyRegistrationOptions>.t1(true),
completionProvider: dynamicRegistrations.completion
? null
: CompletionOptions(
triggerCharacters: dartCompletionTriggerCharacters,
allCommitCharacters: previewCommitCharacters
? dartCompletionCommitCharacters
: null,
resolveProvider: true,
),
hoverProvider: dynamicRegistrations.hover
? null
: Either2<bool, HoverOptions>.t1(true),
signatureHelpProvider: dynamicRegistrations.signatureHelp
? null
: SignatureHelpOptions(
triggerCharacters: dartSignatureHelpTriggerCharacters,
retriggerCharacters: dartSignatureHelpRetriggerCharacters,
),
definitionProvider: dynamicRegistrations.definition
? null
: Either2<bool, DefinitionOptions>.t1(true),
implementationProvider: dynamicRegistrations.implementation
? null
: Either3<bool, ImplementationOptions,
ImplementationRegistrationOptions>.t1(
true,
),
referencesProvider: dynamicRegistrations.references
? null
: Either2<bool, ReferenceOptions>.t1(true),
documentHighlightProvider: dynamicRegistrations.documentHighlights
? null
: Either2<bool, DocumentHighlightOptions>.t1(true),
documentSymbolProvider: dynamicRegistrations.documentSymbol
? null
: Either2<bool, DocumentSymbolOptions>.t1(true),
// "The `CodeActionOptions` return type is only valid if the client
// signals code action literal support via the property
// `textDocument.codeAction.codeActionLiteralSupport`."
codeActionProvider: dynamicRegistrations.codeActions
? null
: codeActionLiteralSupport
? Either2<bool, CodeActionOptions>.t2(CodeActionOptions(
codeActionKinds: DartCodeActionKind.serverSupportedKinds,
))
: Either2<bool, CodeActionOptions>.t1(true),
colorProvider: dynamicRegistrations.colorProvider
? null
: Either3<bool, DocumentColorOptions,
DocumentColorRegistrationOptions>.t3(
DocumentColorRegistrationOptions(documentSelector: [dartFiles])),
documentFormattingProvider: dynamicRegistrations.formatting
? null
: Either2<bool, DocumentFormattingOptions>.t1(enableFormatter),
documentOnTypeFormattingProvider: dynamicRegistrations.typeFormatting
? null
: enableFormatter
? DocumentOnTypeFormattingOptions(
firstTriggerCharacter: dartTypeFormattingCharacters.first,
moreTriggerCharacter:
dartTypeFormattingCharacters.skip(1).toList())
: null,
documentRangeFormattingProvider: dynamicRegistrations.typeFormatting
? null
: Either2<bool, DocumentRangeFormattingOptions>.t1(enableFormatter),
inlayHintProvider: dynamicRegistrations.inlayHints
? null
: Either3<bool, InlayHintOptions, InlayHintRegistrationOptions>.t2(
InlayHintOptions(resolveProvider: false),
),
renameProvider: dynamicRegistrations.rename
? null
: renameOptionsSupport
? Either2<bool, RenameOptions>.t2(
RenameOptions(prepareProvider: true))
: Either2<bool, RenameOptions>.t1(true),
foldingRangeProvider: dynamicRegistrations.folding
? null
: Either3<bool, FoldingRangeOptions,
FoldingRangeRegistrationOptions>.t1(
true,
),
selectionRangeProvider: dynamicRegistrations.selectionRange
? null
: Either3<bool, SelectionRangeOptions,
SelectionRangeRegistrationOptions>.t1(true),
semanticTokensProvider: dynamicRegistrations.semanticTokens
? null
: Either2<SemanticTokensOptions,
SemanticTokensRegistrationOptions>.t1(
SemanticTokensOptions(
legend: semanticTokenLegend.lspLegend,
full: Either2<bool, SemanticTokensOptionsFull>.t2(
SemanticTokensOptionsFull(delta: false),
),
range: Either2<bool, SemanticTokensOptionsRange>.t1(true),
),
),
typeHierarchyProvider: dynamicRegistrations.typeHierarchy
? null
: Either3<bool, TypeHierarchyOptions,
TypeHierarchyRegistrationOptions>.t1(true),
executeCommandProvider: ExecuteCommandOptions(
commands: Commands.serverSupportedCommands,
workDoneProgress: true,
),
workspaceSymbolProvider: Either2<bool, WorkspaceSymbolOptions>.t1(true),
textDocumentSync: features.textDocumentSync.staticRegistration,
callHierarchyProvider: features.callHierarchy.staticRegistration,
completionProvider: features.completion.staticRegistration,
hoverProvider: features.hover.staticRegistration,
signatureHelpProvider: features.signatureHelp.staticRegistration,
definitionProvider: features.definition.staticRegistration,
implementationProvider: features.implementation.staticRegistration,
referencesProvider: features.references.staticRegistration,
documentHighlightProvider: features.documentHighlight.staticRegistration,
documentSymbolProvider: features.documentSymbol.staticRegistration,
codeActionProvider: features.codeActions.staticRegistration,
colorProvider: features.colors.staticRegistration,
documentFormattingProvider: features.format.staticRegistration,
documentOnTypeFormattingProvider:
features.formatOnType.staticRegistration,
documentRangeFormattingProvider: features.formatRange.staticRegistration,
inlayHintProvider: features.inlayHint.staticRegistration,
renameProvider: features.rename.staticRegistration,
foldingRangeProvider: features.foldingRange.staticRegistration,
selectionRangeProvider: features.selectionRange.staticRegistration,
semanticTokensProvider: features.semanticTokens.staticRegistration,
typeDefinitionProvider: features.typeDefinition.staticRegistration,
typeHierarchyProvider: features.typeHierarchy.staticRegistration,
executeCommandProvider: features.executeCommand.staticRegistration,
workspaceSymbolProvider: features.workspaceSymbol.staticRegistration,
workspace: ServerCapabilitiesWorkspace(
workspaceFolders: WorkspaceFoldersServerCapabilities(
supported: true,
changeNotifications: Either2<bool, String>.t1(true),
changeNotifications: features.changeNotifications.staticRegistration,
),
fileOperations: dynamicRegistrations.fileOperations
? null
: FileOperationOptions(
willRename: fileOperationRegistrationOptions,
),
fileOperations: !context.clientDynamic.fileOperations
? FileOperationOptions(
willRename: features.willRename.staticRegistration,
)
: null,
),
);
}
@ -334,247 +211,27 @@ class ServerCapabilitiesComputer {
/// support and it will be up to them to decide which file types they will
/// send requests for.
Future<void> performDynamicRegistration() async {
final pluginTypes = AnalysisServer.supportsPlugins
? _server.pluginManager.plugins
.expand(
(plugin) => plugin.currentSession?.interestingFiles ?? const [])
// All published plugins use something like `*.extension` as
// interestingFiles. Prefix a `**/` so that the glob matches nested
// folders as well.
.map((glob) => TextDocumentFilterWithScheme(
scheme: 'file', pattern: '**/$glob'))
: <TextDocumentFilterWithScheme>[];
final pluginTypesExcludingDart =
pluginTypes.where((filter) => filter.pattern != '**/*.dart');
final fullySupportedTypes = {dartFiles, ...pluginTypes}.toList();
// Add pubspec + analysis options only for synchronisation. We do not support
// things like hovers/formatting/etc. for these files so there's no point
// in having the client send those requests (plus, for things like formatting
// this could result in the editor reporting "multiple formatters installed"
// and prevent a built-in YAML formatter from being selected).
final synchronisedTypes = {
...fullySupportedTypes,
pubspecFile,
analysisOptionsFile,
fixDataFile,
}.toList();
// Completion is supported for some synchronised files that we don't _fully_
// support (eg. YAML). If these gain support for things like hover, we may
// wish to move them to fullySupportedTypes but add an exclusion for formatting.
final completionSupportedTypesExcludingDart = {
// Dart is excluded here at it's registered separately with trigger/commit
// characters.
...pluginTypesExcludingDart,
pubspecFile,
analysisOptionsFile,
fixDataFile,
}.toList();
final context = RegistrationContext(
clientCapabilities: _server.lspClientCapabilities!,
clientConfiguration: _server.lspClientConfiguration,
pluginTypes: pluginTypes,
);
final features = LspFeatures(context);
final registrations = <Registration>[];
final enableFormatter =
_server.lspClientConfiguration.global.enableSdkFormatter;
final previewCommitCharacters =
_server.lspClientConfiguration.global.previewCommitCharacters;
final updateImportsOnRename =
_server.lspClientConfiguration.global.updateImportsOnRename;
/// Helper for creating registrations with IDs.
void register(bool condition, Method method, [ToJsonable? options]) {
if (condition == true) {
registrations.add(Registration(
id: (_lastRegistrationId++).toString(),
method: method.toString(),
registerOptions: options));
}
}
final dynamicRegistrations =
ClientDynamicRegistrations(_server.lspClientCapabilities!.raw);
register(
dynamicRegistrations.textSync,
Method.textDocument_didOpen,
TextDocumentRegistrationOptions(documentSelector: synchronisedTypes),
);
register(
dynamicRegistrations.textSync,
Method.textDocument_didClose,
TextDocumentRegistrationOptions(documentSelector: synchronisedTypes),
);
register(
dynamicRegistrations.textSync,
Method.textDocument_didChange,
TextDocumentChangeRegistrationOptions(
syncKind: TextDocumentSyncKind.Incremental,
documentSelector: synchronisedTypes),
);
// Trigger and commit characters are specific to Dart, so register them
// separately to the others.
register(
dynamicRegistrations.completion,
Method.textDocument_completion,
CompletionRegistrationOptions(
documentSelector: [dartFiles],
triggerCharacters: dartCompletionTriggerCharacters,
allCommitCharacters:
previewCommitCharacters ? dartCompletionCommitCharacters : null,
resolveProvider: true,
),
);
register(
dynamicRegistrations.completion,
Method.textDocument_completion,
CompletionRegistrationOptions(
documentSelector: completionSupportedTypesExcludingDart,
resolveProvider: true,
),
);
register(
dynamicRegistrations.hover,
Method.textDocument_hover,
TextDocumentRegistrationOptions(documentSelector: fullySupportedTypes),
);
register(
dynamicRegistrations.signatureHelp,
Method.textDocument_signatureHelp,
SignatureHelpRegistrationOptions(
documentSelector: fullySupportedTypes,
triggerCharacters: dartSignatureHelpTriggerCharacters,
retriggerCharacters: dartSignatureHelpRetriggerCharacters,
),
);
register(
dynamicRegistrations.references,
Method.textDocument_references,
TextDocumentRegistrationOptions(documentSelector: fullySupportedTypes),
);
register(
dynamicRegistrations.documentHighlights,
Method.textDocument_documentHighlight,
TextDocumentRegistrationOptions(documentSelector: fullySupportedTypes),
);
register(
dynamicRegistrations.documentSymbol,
Method.textDocument_documentSymbol,
TextDocumentRegistrationOptions(documentSelector: fullySupportedTypes),
);
register(
dynamicRegistrations.colorProvider,
// This registration covers both documentColor and colorPresentation.
Method.textDocument_documentColor,
DocumentColorRegistrationOptions(documentSelector: [dartFiles]),
);
register(
enableFormatter && dynamicRegistrations.formatting,
Method.textDocument_formatting,
TextDocumentRegistrationOptions(documentSelector: fullySupportedTypes),
);
register(
enableFormatter && dynamicRegistrations.typeFormatting,
Method.textDocument_onTypeFormatting,
DocumentOnTypeFormattingRegistrationOptions(
documentSelector: [dartFiles], // This one is currently Dart-specific
firstTriggerCharacter: dartTypeFormattingCharacters.first,
moreTriggerCharacter: dartTypeFormattingCharacters.skip(1).toList(),
),
);
register(
enableFormatter && dynamicRegistrations.rangeFormatting,
Method.textDocument_rangeFormatting,
DocumentRangeFormattingRegistrationOptions(
documentSelector: [dartFiles], // This one is currently Dart-specific
),
);
register(
dynamicRegistrations.definition,
Method.textDocument_definition,
TextDocumentRegistrationOptions(documentSelector: fullySupportedTypes),
);
register(
dynamicRegistrations.typeDefinition,
Method.textDocument_typeDefinition,
TextDocumentRegistrationOptions(
documentSelector: [dartFiles], // This one is currently Dart-specific
),
);
register(
dynamicRegistrations.implementation,
Method.textDocument_implementation,
TextDocumentRegistrationOptions(documentSelector: fullySupportedTypes),
);
register(
dynamicRegistrations.codeActions,
Method.textDocument_codeAction,
CodeActionRegistrationOptions(
documentSelector: fullySupportedTypes,
codeActionKinds: DartCodeActionKind.serverSupportedKinds,
),
);
register(
dynamicRegistrations.rename,
Method.textDocument_rename,
RenameRegistrationOptions(
documentSelector: fullySupportedTypes, prepareProvider: true),
);
register(
dynamicRegistrations.folding,
Method.textDocument_foldingRange,
TextDocumentRegistrationOptions(documentSelector: fullySupportedTypes),
);
register(
updateImportsOnRename && dynamicRegistrations.fileOperations,
Method.workspace_willRenameFiles,
fileOperationRegistrationOptions,
);
register(
dynamicRegistrations.didChangeConfiguration,
Method.workspace_didChangeConfiguration,
);
register(
dynamicRegistrations.selectionRange,
Method.textDocument_selectionRange,
SelectionRangeRegistrationOptions(
documentSelector: [dartFiles],
),
);
register(
dynamicRegistrations.callHierarchy,
Method.textDocument_prepareCallHierarchy,
CallHierarchyRegistrationOptions(
documentSelector: [dartFiles],
),
);
register(
dynamicRegistrations.semanticTokens,
CustomMethods.semanticTokenDynamicRegistration,
SemanticTokensRegistrationOptions(
documentSelector: fullySupportedTypes,
legend: semanticTokenLegend.lspLegend,
full: Either2<bool, SemanticTokensOptionsFull>.t2(
SemanticTokensOptionsFull(delta: false),
// Collect dynamic registrations for all features.
final dynamicRegistrations = features.allFeatures
.where((feature) => feature.supportsDynamic)
.expand((feature) => feature.dynamicRegistrations);
for (final (method, options) in dynamicRegistrations) {
registrations.add(
Registration(
id: (_lastRegistrationId++).toString(),
method: method.toString(),
registerOptions: options,
),
range: Either2<bool, SemanticTokensOptionsRange>.t1(true),
),
);
register(
dynamicRegistrations.typeHierarchy,
Method.textDocument_prepareTypeHierarchy,
TypeHierarchyRegistrationOptions(
documentSelector: [dartFiles],
),
);
register(
dynamicRegistrations.inlayHints,
Method.textDocument_inlayHint,
InlayHintRegistrationOptions(
documentSelector: [dartFiles],
resolveProvider: false,
),
);
);
}
await _applyRegistrations(registrations);
}

View file

@ -416,8 +416,7 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
.any((ds) => ds.pattern == '**/analysis_options.yaml'),
isTrue);
expect(rename,
equals(ServerCapabilitiesComputer.fileOperationRegistrationOptions));
expect(rename, equals(fileOperationRegistrationOptions));
}
Future<void> test_dynamicRegistration_notSupportedByClient() async {
@ -463,7 +462,7 @@ class InitializationTest extends AbstractLspAnalysisServerTest {
expect(initResult.capabilities.renameProvider, isNotNull);
expect(initResult.capabilities.foldingRangeProvider, isNotNull);
expect(initResult.capabilities.workspace!.fileOperations!.willRename,
equals(ServerCapabilitiesComputer.fileOperationRegistrationOptions));
equals(fileOperationRegistrationOptions));
expect(initResult.capabilities.selectionRangeProvider, isNotNull);
expect(initResult.capabilities.semanticTokensProvider, isNotNull);