[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>
diff --git a/pkg/analysis_server/lib/src/lsp/constants.dart b/pkg/analysis_server/lib/src/lsp/constants.dart
index 24536d6..d9e2462 100644
--- a/pkg/analysis_server/lib/src/lsp/constants.dart
+++ b/pkg/analysis_server/lib/src/lsp/constants.dart
@@ -45,11 +45,48 @@
 /// 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
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_call_hierarchy.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_call_hierarchy.dart
index 85d1bc0..aa19346 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_call_hierarchy.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_call_hierarchy.dart
@@ -9,13 +9,36 @@
 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<
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_change_workspace_folders.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_change_workspace_folders.dart
index 5c0d7c9..311e2f6 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_change_workspace_folders.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_change_workspace_folders.dart
@@ -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 @@
     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;
+}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
index b19a285..0d340ca 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
@@ -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/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 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 {
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
index a89c462..f12cc30 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
@@ -7,8 +7,10 @@
 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 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.
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_definition.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_definition.dart
index cc69056..8365596 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_definition.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_definition.dart
@@ -7,6 +7,7 @@
     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/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 @@
     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;
+}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_document_color.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_document_color.dart
index b0918c4..71a55a3 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_document_color.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_document_color.dart
@@ -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 @@
     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;
+}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_document_highlights.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_document_highlights.dart
index 11cc219..0fd6f21 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_document_highlights.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_document_highlights.dart
@@ -7,6 +7,9 @@
 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 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;
+}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_document_symbols.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_document_symbols.dart
index 84c5015..29d611e 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_document_symbols.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_document_symbols.dart
@@ -7,10 +7,13 @@
 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 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;
+}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_execute_command.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_execute_command.dart
index afdaab3..91042de 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_execute_command.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_execute_command.dart
@@ -14,6 +14,7 @@
 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 @@
     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;
+}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_folding.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_folding.dart
index b7d7a06..0385114 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_folding.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_folding.dart
@@ -6,9 +6,13 @@
 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 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;
+}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_format_on_type.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_format_on_type.dart
index 84f5e15..a0e5e8c 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_format_on_type.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_format_on_type.dart
@@ -6,8 +6,11 @@
 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 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;
+}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_format_range.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_format_range.dart
index 1630647..549c2df 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_format_range.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_format_range.dart
@@ -6,8 +6,11 @@
 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 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;
+}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_formatting.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_formatting.dart
index 57224d7..1e21e27 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_formatting.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_formatting.dart
@@ -6,8 +6,11 @@
 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 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;
+}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_hover.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_hover.dart
index 3689e87..2aadefd 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_hover.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_hover.dart
@@ -8,9 +8,12 @@
 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 @@
     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;
+}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_implementation.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_implementation.dart
index 09ebebf..12293a8 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_implementation.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_implementation.dart
@@ -6,6 +6,7 @@
     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/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 @@
     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;
+}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_inlay_hint.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_inlay_hint.dart
index e5900df..44634ab 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_inlay_hint.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_inlay_hint.dart
@@ -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 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;
+}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_references.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_references.dart
index c9f0142..30a307c 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_references.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_references.dart
@@ -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/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 @@
     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;
+}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_rename.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_rename.dart
index 38c92f85..3e5dd08 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_rename.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_rename.dart
@@ -8,12 +8,15 @@
 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 @@
     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;
+}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_selection_range.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_selection_range.dart
index 6946b0a..cc2c703 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_selection_range.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_selection_range.dart
@@ -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 @@
         .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;
+}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_semantic_tokens.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_semantic_tokens.dart
index 7e6c2ab..e2d78b4 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_semantic_tokens.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_semantic_tokens.dart
@@ -6,12 +6,18 @@
 
 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 @@
           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;
+}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_signature_help.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_signature_help.dart
index de6af5b..9f7372e 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_signature_help.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_signature_help.dart
@@ -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 @@
     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;
+}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_states.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_states.dart
index fb8ecb0..8dc007c 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_states.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_states.dart
@@ -80,7 +80,7 @@
     ReferencesHandler.new,
     CodeActionHandler.new,
     ExecuteCommandHandler.new,
-    WorkspaceFoldersHandler.new,
+    ChangeWorkspaceFoldersHandler.new,
     PrepareRenameHandler.new,
     RenameHandler.new,
     FoldingHandler.new,
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_text_document_changes.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_text_document_changes.dart
index a304df6b..a50bf37 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_text_document_changes.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_text_document_changes.dart
@@ -7,8 +7,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/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 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();
+  }
+}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_type_definition.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_type_definition.dart
index 1f09058..6ba58b9 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_type_definition.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_type_definition.dart
@@ -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_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 @@
     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;
+}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_type_hierarchy.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_type_hierarchy.dart
index a3572c2..a4ed0c6 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_type_hierarchy.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_type_hierarchy.dart
@@ -8,14 +8,19 @@
 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 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 {
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_will_rename_files.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_will_rename_files.dart
index 9dcaec4..880f546 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_will_rename_files.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_will_rename_files.dart
@@ -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 @@
     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;
+}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_workspace_configuration.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_workspace_configuration.dart
index 3d6f45f..9da23ed 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_workspace_configuration.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_workspace_configuration.dart
@@ -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 @@
     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;
+}
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_workspace_symbols.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_workspace_symbols.dart
index 8c90d87..74a06b2 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_workspace_symbols.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_workspace_symbols.dart
@@ -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 @@
         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;
+}
diff --git a/pkg/analysis_server/lib/src/lsp/registration/feature_registration.dart b/pkg/analysis_server/lib/src/lsp/registration/feature_registration.dart
new file mode 100644
index 0000000..790cc9d
--- /dev/null
+++ b/pkg/analysis_server/lib/src/lsp/registration/feature_registration.dart
@@ -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;
+}
diff --git a/pkg/analysis_server/lib/src/lsp/server_capabilities_computer.dart b/pkg/analysis_server/lib/src/lsp/server_capabilities_computer.dart
index 0fe0563..dde1012e 100644
--- a/pkg/analysis_server/lib/src/lsp/server_capabilities_computer.dart
+++ b/pkg/analysis_server/lib/src/lsp/server_capabilities_computer.dart
@@ -7,7 +7,7 @@
 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 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 @@
   /// 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);
   }
diff --git a/pkg/analysis_server/test/lsp/initialization_test.dart b/pkg/analysis_server/test/lsp/initialization_test.dart
index f703e14..3c30d50 100644
--- a/pkg/analysis_server/test/lsp/initialization_test.dart
+++ b/pkg/analysis_server/test/lsp/initialization_test.dart
@@ -416,8 +416,7 @@
             .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 @@
     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);