From f1ceed2ba74bdaa219209837511a9bb28a6946a2 Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Wed, 3 Jun 2026 08:47:33 -0700 Subject: [PATCH] [analysis_server] Add command/resolve to support using Interactive Forms in refactors This adds support for the `command/resolve` request to support the new Interactive Forms functionality, and updates the refactor processes to use it instead of the original protocol when the client indicates support. Currently only the "Move to File" refactor uses this functionality (matching the previous version) and it requires the `dart.experimentalInteractiveForms` setting be enabled for it to be used (in case we find bugs while developing the front-end that require incompatible changes). Fixes https://github.com/dart-lang/sdk/issues/63371 Change-Id: I414a41fa2958ca9dcefe097f35ba28f3fa2fe367 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/508121 Reviewed-by: Samuel Rawlins Reviewed-by: Brian Wilkerson --- .../lib/src/lsp/client_capabilities.dart | 17 ++ .../lib/src/lsp/client_configuration.dart | 10 + .../lib/src/lsp/constants.dart | 3 + .../commands/refactor_command_executor.dart | 2 +- .../refactor_command_handler_mixin.dart | 7 +- .../commands/refactor_command_resolver.dart | 68 +++++ .../custom/handler_command_resolve.dart | 78 +++++ .../lib/src/lsp/handlers/handler_states.dart | 2 + .../src/lsp/server_capabilities_computer.dart | 9 + .../framework/refactoring_processor.dart | 24 +- .../framework/refactoring_producer.dart | 6 + .../refactoring/move_top_level_to_file.dart | 61 +++- .../test/lsp/commands/resolve_test.dart | 283 ++++++++++++++++++ .../test/lsp/commands/test_all.dart | 2 + .../test/lsp/request_helpers_mixin.dart | 10 + .../test/lsp/server_abstract.dart | 12 + .../move_top_level_to_file_test.dart | 182 ++++++++--- .../test/support/interactive_forms.dart | 59 ++++ pkg/analysis_server/tool/lsp_spec/README.md | 192 ++++++------ .../lsp_spec/custom/interactive_forms.dart | 2 +- .../tool/lsp_spec/generate_all.dart | 2 +- 21 files changed, 887 insertions(+), 144 deletions(-) create mode 100644 pkg/analysis_server/lib/src/lsp/handlers/commands/refactor_command_resolver.dart create mode 100644 pkg/analysis_server/lib/src/lsp/handlers/custom/handler_command_resolve.dart create mode 100644 pkg/analysis_server/test/lsp/commands/resolve_test.dart diff --git a/pkg/analysis_server/lib/src/lsp/client_capabilities.dart b/pkg/analysis_server/lib/src/lsp/client_capabilities.dart index 0e119046531..ed3898e727d 100644 --- a/pkg/analysis_server/lib/src/lsp/client_capabilities.dart +++ b/pkg/analysis_server/lib/src/lsp/client_capabilities.dart @@ -105,6 +105,7 @@ class LspClientCapabilities { final bool completionDefaultTextMode; final bool experimentalSnippetTextEdit; final Set codeActionCommandParameterSupportedKinds; + final Set supportedInteractiveFormInputTypes; final bool supportsShowMessageRequest; /// A set of commands that exist on the client that the server may call. @@ -225,6 +226,8 @@ class LspClientCapabilities { experimentalSnippetTextEdit: experimental.snippetTextEdit, codeActionCommandParameterSupportedKinds: experimental.commandParameterKinds, + supportedInteractiveFormInputTypes: + experimental.interactiveFormInputTypes, supportsShowMessageRequest: experimental.showMessageRequest, supportedCommands: experimental.commands, experimentalCapabilitiesErrors: experimental.errors, @@ -265,6 +268,7 @@ class LspClientCapabilities { required this.completionDefaultTextMode, required this.experimentalSnippetTextEdit, required this.codeActionCommandParameterSupportedKinds, + required this.supportedInteractiveFormInputTypes, required this.supportsShowMessageRequest, required this.supportedCommands, required this.experimentalCapabilitiesErrors, @@ -291,12 +295,14 @@ class _ExperimentalClientCapabilities { final bool snippetTextEdit; final Set commandParameterKinds; + final Set interactiveFormInputTypes; final Set commands; final bool showMessageRequest; new({ required this.snippetTextEdit, required this.commandParameterKinds, + required this.interactiveFormInputTypes, required this.commands, required this.showMessageRequest, required this.errors, @@ -362,6 +368,16 @@ class _ExperimentalClientCapabilities { commandParameters['supportedKinds'], ); + // Interactive Forms. + var interactiveForms = expectMap( + '.interactiveResolve', + experimental['interactiveResolve'], + ); + var interactiveFormInputTypes = expectNullableStringSet( + '.interactiveResolve.inputTypes', + interactiveForms?['inputTypes'], + ); + // Executable commands. var commands = expectNullableStringSet( '.commands', @@ -384,6 +400,7 @@ class _ExperimentalClientCapabilities { return _ExperimentalClientCapabilities( snippetTextEdit: snippetTextEdit ?? false, commandParameterKinds: commandParameterKinds ?? {}, + interactiveFormInputTypes: interactiveFormInputTypes ?? {}, commands: commands ?? {}, showMessageRequest: showMessageRequest ?? false, errors: errors, diff --git a/pkg/analysis_server/lib/src/lsp/client_configuration.dart b/pkg/analysis_server/lib/src/lsp/client_configuration.dart index 3b7f7c048a5..d2dc74dd2b1 100644 --- a/pkg/analysis_server/lib/src/lsp/client_configuration.dart +++ b/pkg/analysis_server/lib/src/lsp/client_configuration.dart @@ -299,6 +299,16 @@ class LspGlobalClientConfiguration extends LspResourceClientConfiguration { bool get experimentalInlineValuesProperties => _settings['experimentalInlineValuesProperties'] as bool? ?? false; + /// Whether the newer experimental Interactive Forms (that can be used by + /// refactors instead of the original Dart-specified mechanism for collecting + /// user input) are enabled by the client. + /// + /// This is a temporary flag during development that will be checked by both + /// server + client to allow controlling which versions of each will enable + /// the functionality (in case of breaking changes during dev). + bool get experimentalInteractiveForms => + _settings['experimentalInteractiveForms'] as bool? ?? false; + /// A flag for enabling interactive refactors flagged as experimental. /// /// This flag is likely to be used by both analysis server developers (working diff --git a/pkg/analysis_server/lib/src/lsp/constants.dart b/pkg/analysis_server/lib/src/lsp/constants.dart index a0237068cb1..91922e2cfd1 100644 --- a/pkg/analysis_server/lib/src/lsp/constants.dart +++ b/pkg/analysis_server/lib/src/lsp/constants.dart @@ -196,6 +196,9 @@ abstract final class CustomMethods { 'dart/updateDiagnosticInformation', ); + /// Used for resolving commands to support interactive forms. + static const resolveCommand = Method('command/resolve'); + /// An experimental 'echo' handler that can used by tests to verify /// experimental handlers only show up when requested. static const experimentalEcho = Method('experimental/echo'); diff --git a/pkg/analysis_server/lib/src/lsp/handlers/commands/refactor_command_executor.dart b/pkg/analysis_server/lib/src/lsp/handlers/commands/refactor_command_executor.dart index 1cdf2c95fa6..d114180ed0c 100644 --- a/pkg/analysis_server/lib/src/lsp/handlers/commands/refactor_command_executor.dart +++ b/pkg/analysis_server/lib/src/lsp/handlers/commands/refactor_command_executor.dart @@ -20,7 +20,7 @@ import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dar /// A command handler that executes commands used to implement refactorings /// that can describe their inputs (either via the original Dart protocol or -/// the updated Go-specified protocol). +/// the updated Interactive Forms protocol). class RefactorCommandExecutor extends SimpleEditCommandHandler with RefactorCommandHandlerMixin { @override diff --git a/pkg/analysis_server/lib/src/lsp/handlers/commands/refactor_command_handler_mixin.dart b/pkg/analysis_server/lib/src/lsp/handlers/commands/refactor_command_handler_mixin.dart index e00dea692d5..f7af3163c5f 100644 --- a/pkg/analysis_server/lib/src/lsp/handlers/commands/refactor_command_handler_mixin.dart +++ b/pkg/analysis_server/lib/src/lsp/handlers/commands/refactor_command_handler_mixin.dart @@ -2,6 +2,10 @@ // 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. +/// @docImport 'package:analysis_server/src/lsp/handlers/commands/refactor_command_executor.dart'; +/// @docImport 'package:analysis_server/src/lsp/handlers/commands/refactor_command_resolver.dart'; +library; + import 'package:analysis_server/src/lsp/client_capabilities.dart'; import 'package:analysis_server/src/lsp/constants.dart'; import 'package:analysis_server/src/lsp/error_or.dart'; @@ -12,7 +16,8 @@ import 'package:analyzer/dart/analysis/results.dart'; import 'package:language_server_protocol/protocol_custom_generated.dart'; import 'package:language_server_protocol/protocol_generated.dart'; -/// A mixin with functionality common to handlers for refactor commands. +/// A mixin with functionality common to handlers for refactor commands, +/// such as [RefactorCommandResolver] and [RefactorCommandExecutor]. mixin RefactorCommandHandlerMixin on HandlerHelperMixin, Handler { Future> execute( ProgressReporter progress, diff --git a/pkg/analysis_server/lib/src/lsp/handlers/commands/refactor_command_resolver.dart b/pkg/analysis_server/lib/src/lsp/handlers/commands/refactor_command_resolver.dart new file mode 100644 index 00000000000..3b9e095ffb4 --- /dev/null +++ b/pkg/analysis_server/lib/src/lsp/handlers/commands/refactor_command_resolver.dart @@ -0,0 +1,68 @@ +// Copyright (c) 2026, 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/analysis_server.dart'; +import 'package:analysis_server/src/lsp/client_capabilities.dart'; +import 'package:analysis_server/src/lsp/error_or.dart'; +import 'package:analysis_server/src/lsp/handlers/commands/refactor_command_handler_mixin.dart'; +import 'package:analysis_server/src/lsp/handlers/handlers.dart'; +import 'package:analysis_server/src/lsp/progress.dart'; +import 'package:analysis_server/src/services/refactoring/framework/refactoring_context.dart'; +import 'package:analysis_server/src/services/refactoring/framework/refactoring_processor.dart'; +import 'package:analysis_server/src/services/refactoring/framework/refactoring_producer.dart'; +import 'package:analyzer/dart/analysis/results.dart'; + +/// A sub-handler for `command/resolve` that handles resolving commands for +/// refactors by delegating them to the appropriate [RefactoringProducer]. +/// +/// Some of the implementation here comes from [RefactorCommandHandlerMixin] +/// which has shared logic used for both resolving and executing refactors +/// (such as building the [RefactoringContext] from the commands arguments). +class RefactorCommandResolver + with + HandlerHelperMixin, + Handler, + RefactorCommandHandlerMixin { + final RefactoringProducerGenerator generator; + + @override + final AnalysisServer server; + + /// The client-supplied command to be resolved. + final InteractiveExecuteCommandParams command; + + new(this.server, this.generator, this.command); + + @override + Future> execute( + ProgressReporter progress, + ResolvedLibraryResult library, + ResolvedUnitResult unit, + LspClientCapabilities clientCapabilities, + RefactoringContext context, + List arguments, + ) async { + var producer = generator(context); + + if (!producer.isAvailable()) { + // Generally this shouldn't happen (because we shouldn't have produced a + // command that isn't valid), but it could if the client allowed the file + // to be modified and didn't cancel. + return error( + ErrorCodes.InvalidParams, + 'Refactor command is no longer valid at this location', + ); + } + + // Delegate to the refactoring producer so it can handle custom validation + // etc. + if (producer is ParameterizedRefactoringProducer) { + return await producer.resolve(command); + } + + // Otherwise, pass the original command back as-is. + return success(command); + } +} diff --git a/pkg/analysis_server/lib/src/lsp/handlers/custom/handler_command_resolve.dart b/pkg/analysis_server/lib/src/lsp/handlers/custom/handler_command_resolve.dart new file mode 100644 index 00000000000..c3069fd4333 --- /dev/null +++ b/pkg/analysis_server/lib/src/lsp/handlers/custom/handler_command_resolve.dart @@ -0,0 +1,78 @@ +// Copyright (c) 2026, 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. + +/// @docImport 'package:analysis_server/src/services/refactoring/framework/refactoring_producer.dart'; +library; + +import 'package:analysis_server/lsp_protocol/protocol.dart'; +import 'package:analysis_server/src/lsp/constants.dart'; +import 'package:analysis_server/src/lsp/error_or.dart'; +import 'package:analysis_server/src/lsp/handlers/commands/refactor_command_resolver.dart'; +import 'package:analysis_server/src/lsp/handlers/handlers.dart'; +import 'package:analysis_server/src/lsp/progress.dart'; +import 'package:analysis_server/src/services/refactoring/framework/refactoring_processor.dart'; + +/// A handler for the [CustomMethods.resolveCommand] custom request that allows +/// collecting user input via interactive form fields. +/// +/// This handler is the main entry point for the LSP request and delegates to +/// sub-handlers like [RefactorCommandResolver] depending on the command that +/// needs resolving. +class CommandResolveHandler + extends + SharedMessageHandler< + InteractiveExecuteCommandParams, + InteractiveExecuteCommandParams + > { + new(super.server); + + @override + Method get handlesMessage => CustomMethods.resolveCommand; + + @override + LspJsonHandler get jsonHandler => + InteractiveExecuteCommandParams.jsonHandler; + + @override + // This command is used as part of interactive forms and not expected to be + // used by non-editor clients. + bool get requiresTrustedCaller => true; + + @override + Future> handle( + InteractiveExecuteCommandParams command, + MessageInfo message, + CancellationToken token, + ) async { + if (RefactoringProcessor.generators[command.command] case var generator?) { + return await _handleRefactorCommand(command, generator, message, token); + } + + return success(command); + } + + /// Handles resolving a command that relates to a refactor by using + /// [RefactorCommandResolver]. + Future> _handleRefactorCommand( + InteractiveExecuteCommandParams command, + RefactoringProducerGenerator generator, + MessageInfo message, + CancellationToken token, + ) async { + if (command.arguments case [Map arguments]) { + var resolver = RefactorCommandResolver(server, generator, command); + return await resolver.handle( + message, + arguments, + ProgressReporter.noop, + token, + ); + } else { + return error( + ErrorCodes.InvalidParams, + 'Refactor commands should always have exactly one argument, which is a map', + ); + } + } +} 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 452c622364d..35bf1419ee7 100644 --- a/pkg/analysis_server/lib/src/lsp/handlers/handler_states.dart +++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_states.dart @@ -12,6 +12,7 @@ import 'package:analysis_server/src/lsp/handlers/custom/editable_arguments/handl import 'package:analysis_server/src/lsp/handlers/custom/editable_arguments/handler_editable_arguments.dart'; import 'package:analysis_server/src/lsp/handlers/custom/handler_augmentation.dart'; import 'package:analysis_server/src/lsp/handlers/custom/handler_augmented.dart'; +import 'package:analysis_server/src/lsp/handlers/custom/handler_command_resolve.dart'; import 'package:analysis_server/src/lsp/handlers/custom/handler_connect_to_dtd.dart'; import 'package:analysis_server/src/lsp/handlers/custom/handler_diagnostic_server.dart'; import 'package:analysis_server/src/lsp/handlers/custom/handler_experimental_echo.dart'; @@ -125,6 +126,7 @@ class InitializedStateMessageHandler extends ServerStateMessageHandler { AugmentedHandler.new, CodeActionHandler.new, CodeLensHandler.new, + CommandResolveHandler.new, ConnectToDtdHandler.new, DiagnosticServerHandler.new, DocumentColorHandler.new, 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 41ccebaadc2..c8171ced47d 100644 --- a/pkg/analysis_server/lib/src/lsp/server_capabilities_computer.dart +++ b/pkg/analysis_server/lib/src/lsp/server_capabilities_computer.dart @@ -223,6 +223,15 @@ class ServerCapabilitiesComputer { // Indicate that we support the 'updateDiagnosticInformation' // custom request. 'updateDiagnosticInformation': {}, + + // Interactive Forms support. + 'interactiveResolveProvider': { + // The kinds of interactive resolutions that the server supports. + // For example, "command" indicates that the server supports resolving + // `ExecuteCommandParams` interactively through "command/resolve". + 'kinds': ['command'], + }, + 'textDocument': { // These properties can be used by the client to know that we support // custom methods like `dart/textDocument/augmented`. diff --git a/pkg/analysis_server/lib/src/services/refactoring/framework/refactoring_processor.dart b/pkg/analysis_server/lib/src/services/refactoring/framework/refactoring_processor.dart index 61dc8e41597..6a56e245459 100644 --- a/pkg/analysis_server/lib/src/services/refactoring/framework/refactoring_processor.dart +++ b/pkg/analysis_server/lib/src/services/refactoring/framework/refactoring_processor.dart @@ -72,6 +72,22 @@ class RefactoringProcessor { return; } + var server = context.server; + var interactiveFormsEnabled = + // Temporary flag to ensure this is opt-in during development. + server.lspClientConfiguration.global.experimentalInteractiveForms && + // Client has shown it has support by providing at least one + // input kind that it supports. + // It is up to the individual refactors to handle the specific + // kinds of input that are supported, this check is just to know if + // we will use Interactive Forms instead of the original + // Dart-specified self-described refactors. + (context + .clientCapabilities + ?.supportedInteractiveFormInputTypes + .isNotEmpty ?? + false); + var parameters = producer is ParameterizedRefactoringProducer ? producer.parameters : []; @@ -107,7 +123,13 @@ class RefactoringProcessor { parameters.map((param) => param.defaultValue).toList(), ), ), - data: {'parameters': parameters}, + // Only include the parameters in data if interactive forms are NOT + // enabled, because if they are this will be handled by + // `command/resolve` and we don't want to trigger the old version + // on the client. + data: parameters.isNotEmpty && !interactiveFormsEnabled + ? {'parameters': parameters} + : null, ), ); _performance?.producerTimings.add(( diff --git a/pkg/analysis_server/lib/src/services/refactoring/framework/refactoring_producer.dart b/pkg/analysis_server/lib/src/services/refactoring/framework/refactoring_producer.dart index 00588da0566..e12d701e80f 100644 --- a/pkg/analysis_server/lib/src/services/refactoring/framework/refactoring_producer.dart +++ b/pkg/analysis_server/lib/src/services/refactoring/framework/refactoring_producer.dart @@ -2,6 +2,7 @@ // 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/src/lsp/error_or.dart'; import 'package:analysis_server/src/services/refactoring/framework/refactoring_context.dart'; import 'package:analysis_server/src/services/refactoring/framework/refactoring_processor.dart'; import 'package:analysis_server/src/services/search/search_engine.dart'; @@ -40,6 +41,11 @@ abstract class ParameterizedRefactoringProducer extends RefactoringProducer { List buildCommandArguments(List args) { return RefactoringProcessor.buildCommandArguments(refactoringContext, args); } + + /// Resolves command arguments using the interactive forms functionality. + Future> resolve( + InteractiveExecuteCommandParams command, + ); } /// An object that can compute a refactoring in a Dart file. diff --git a/pkg/analysis_server/lib/src/services/refactoring/move_top_level_to_file.dart b/pkg/analysis_server/lib/src/services/refactoring/move_top_level_to_file.dart index 517b16ae37f..d8d1579870e 100644 --- a/pkg/analysis_server/lib/src/services/refactoring/move_top_level_to_file.dart +++ b/pkg/analysis_server/lib/src/services/refactoring/move_top_level_to_file.dart @@ -3,8 +3,11 @@ // BSD-style license that can be found in the LICENSE file. import 'package:analysis_server/src/lsp/constants.dart'; +import 'package:analysis_server/src/lsp/error_or.dart'; +import 'package:analysis_server/src/services/interactive_forms/interactive_forms.dart'; import 'package:analysis_server/src/services/refactoring/framework/refactoring_producer.dart'; import 'package:analysis_server/src/utilities/extensions/ast.dart'; +import 'package:analysis_server/src/utilities/extensions/list.dart'; import 'package:analysis_server/src/utilities/extensions/string.dart'; import 'package:analysis_server/src/utilities/import_analyzer.dart'; import 'package:analyzer/dart/ast/ast.dart'; @@ -16,7 +19,7 @@ import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dar import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart'; import 'package:analyzer_plugin/utilities/range_factory.dart'; import 'package:language_server_protocol/protocol_custom_generated.dart' - show CommandParameter, SaveUriCommandParameter; + hide Element; import 'package:language_server_protocol/protocol_generated.dart'; /// A refactoring that will move one or more top-level declarations to a @@ -71,8 +74,8 @@ class MoveTopLevelToFile extends ParameterizedRefactoringProducer { _initializeFromMembers(members); var pathContext = refactoringContext.server.resourceProvider.pathContext; var sourcePath = members.containingFile; - // TODO(dantup): Add refactor-specific validation for incoming arguments. - // Argument is a String URI. + // Fields are validated as part of resolve(). We'll keep showing the + // form inputs until all the fields have valid answers. var destinationUri = Uri.parse(commandArguments[0] as String); var destinationFilePath = pathContext.fromUri(destinationUri); @@ -207,6 +210,34 @@ class MoveTopLevelToFile extends ParameterizedRefactoringProducer { return false; } + /// Handles resolving the command to execute this refactor using Interactive + /// Forms. + @override + Future> resolve( + InteractiveExecuteCommandParams command, + ) async { + var commandArguments = command.arguments; + if (commandArguments == null) { + return error( + ErrorCodes.InvalidParams, + 'Refactor commands must have arguments', + ); + } + + var form = _createInteractiveForm() + ..processResponse(command.formAnswers ?? []); + + return success( + InteractiveExecuteCommandParams( + command: command.command, + arguments: buildCommandArguments(form.answers), + data: command.data, + formFields: form.clientFields.nullIfEmpty, + formAnswers: form.clientAnswers.nullIfEmpty, + ), + ); + } + /// Use the [builder] to add the imports that need to be added to the library /// to which the code is being moved based on the information in the import /// [analyzer]. @@ -235,6 +266,30 @@ class MoveTopLevelToFile extends ParameterizedRefactoringProducer { } } + /// Builds the [InteractiveForm] to collect input for this refactor. + InteractiveForm _createInteractiveForm() { + var destinationUriField = FormField( + id: 'destinationUri', + description: 'Move to file', + required: true, + defaultValue: refactoringContext.server.pathContext + .toUri(defaultFilePath) + .toString(), + type: FormFieldTypeFile(filters: ['*.dart']), + ); + + var supportedInteractiveFormInputTypes = + refactoringContext + .clientCapabilities + ?.supportedInteractiveFormInputTypes ?? + {}; + + return InteractiveForm( + supportedInteractiveFormInputTypes: supportedInteractiveFormInputTypes, + fields: [destinationUriField], + ); + } + /// Initialize the [title] and [defaultFilePath] based on the [members] being /// moved. void _initializeFromMembers(_MembersToMove members) { diff --git a/pkg/analysis_server/test/lsp/commands/resolve_test.dart b/pkg/analysis_server/test/lsp/commands/resolve_test.dart new file mode 100644 index 00000000000..fe0b625ed99 --- /dev/null +++ b/pkg/analysis_server/test/lsp/commands/resolve_test.dart @@ -0,0 +1,283 @@ +// Copyright (c) 2026, 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/src/services/refactoring/move_top_level_to_file.dart'; +import 'package:language_server_protocol/protocol_custom_generated.dart'; +import 'package:language_server_protocol/protocol_generated.dart'; +import 'package:matcher/matcher.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +import '../../src/services/refactoring/refactoring_test_support.dart'; +import '../../support/interactive_forms.dart'; +import '../../utils/lsp_protocol_extensions.dart'; +import '../server_abstract.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(CommandResolveTest); + defineReflectiveTests(CommandResolveFileInputTest); + }); +} + +/// Tests file inputs in `command/resolve` using the MoveToFile refactor. +@reflectiveTest +class CommandResolveFileInputTest extends RefactoringTest { + /// Simple file content with a single class named 'A'. + final simpleClassContent = ''' +class ^A {} +'''; + + /// The title of the refactor when using [simpleClassContent]. + final simpleClassRefactorTitle = "Move 'A' to file"; + + @override + String get refactoringCommandId => MoveTopLevelToFile.commandName; + + @override + void setUp() { + super.setUp(); + + // Most of the tests here assume we support file. Tests that do not will + // explicitly unset this. + setSupportedInteractiveFormInputKinds({'file'}); + + // Move to file also requires file create support. + setFileCreateSupport(); + } + + Future test_acceptsValidAnswers() async { + addTestSource(simpleClassContent); + var newFilePath = join(projectFolderPath, 'lib', 'valid_destination.dart'); + var newFileUri = Uri.file(newFilePath); + + await initializeServer(); + var action = await expectCodeActionWithTitle(simpleClassRefactorTitle); + var command = action.asCommand; + + // Resolve the command, which should add the outstanding form fields. + var resolvedCommand = await resolveCommand( + ExecuteCommandParams( + command: command.command, + arguments: command.arguments, + ), + ); + + // Resolve again, with a valid answer. + expect(resolvedCommand.formFields, hasLength(1)); + var field = resolvedCommand.formFields!.single; + resolvedCommand = await resolveCommand( + InteractiveExecuteCommandParams( + command: resolvedCommand.command, + arguments: resolvedCommand.arguments, + formFields: resolvedCommand.formFields, + formAnswers: [field.answer(newFileUri.toString())], // Valid answer. + ), + ); + + // Check that the form field has gone, and that the valid answer was moved + // into the command arguments. + expect(resolvedCommand.formFields, isNull); // No more fields to complete. + var arguments = getRefactorCommandArguments(resolvedCommand.arguments); + expect(arguments, hasLength(1)); + expect(arguments.single, newFileUri.toString()); + } + + Future test_formFields_notSupported() async { + // If we don't support the 'file' kind, then we shouldn't get back any + // form fields, only the default. + setSupportedInteractiveFormInputKinds({'number'}); + + addTestSource(simpleClassContent); + var newFilePath = join(projectFolderPath, 'lib', 'a.dart'); + var newFileUri = Uri.file(newFilePath); + + await initializeServer(); + var action = await expectCodeActionWithTitle(simpleClassRefactorTitle); + var command = action.asCommand; + + // Resolve the command, which would normally add the form fields, but the + // only one used here is not supported by us. + var resolvedCommand = await resolveCommand( + ExecuteCommandParams( + command: command.command, + arguments: command.arguments, + ), + ); + + // Check basics. + expect(resolvedCommand.command, command.command); + expect(resolvedCommand.arguments, command.arguments); + expect(resolvedCommand.formAnswers, isNull); + expect(resolvedCommand.formFields, isNull); + + // Check the arguments to the command contain the default value so the + // command will still work without the user prompt. + var refactorArguments = getRefactorCommandArguments( + resolvedCommand.arguments, + ); + expect(refactorArguments, hasLength(1)); + expect(refactorArguments.single, newFileUri.toString()); + } + + Future test_formFields_supported() async { + addTestSource(simpleClassContent); + var newFilePath = join(projectFolderPath, 'lib', 'a.dart'); + var newFileUri = Uri.file(newFilePath); + + await initializeServer(); + var action = await expectCodeActionWithTitle(simpleClassRefactorTitle); + var command = action.asCommand; + + // Resolve the command, which should add the outstanding form fields. + var resolvedCommand = await resolveCommand( + ExecuteCommandParams( + command: command.command, + arguments: command.arguments, + ), + ); + + // Check basics. + expect(resolvedCommand.command, command.command); + expect(resolvedCommand.arguments, command.arguments); + expect(resolvedCommand.formAnswers, isNull); + expect(resolvedCommand.formFields, hasLength(1)); + + // Check the form field is what we'd expect. + var field = resolvedCommand.formFields!.single; + expect(field.type.kind, 'file'); + expect(field.description, 'Move to file'); + expect(field.defaultValue, newFileUri.toString()); + expect(field.error, isNull); + } + + Future test_validates_incorrectType() async { + addTestSource(simpleClassContent); + + await initializeServer(); + var action = await expectCodeActionWithTitle(simpleClassRefactorTitle); + var command = action.asCommand; + + // Resolve the command, which should add the outstanding form fields. + var resolvedCommand = await resolveCommand( + ExecuteCommandParams( + command: command.command, + arguments: command.arguments, + ), + ); + + // Resolve again, with an incorrect type for the URI. + expect(resolvedCommand.formFields, hasLength(1)); + var field = resolvedCommand.formFields!.single; + resolvedCommand = await resolveCommand( + InteractiveExecuteCommandParams( + command: resolvedCommand.command, + arguments: resolvedCommand.arguments, + formFields: resolvedCommand.formFields, + formAnswers: [field.answer(true)], // Wrong type. + ), + ); + + // Check the field has a validation error and the current value is + // preserved. + expect(resolvedCommand.formFields, hasLength(1)); + field = resolvedCommand.formFields!.single; + expect(field.error, contains('valid file:// URI')); + expect(resolvedCommand.formAnswers!.single, field.answer(true)); + } + + Future test_validates_invalidUri() async { + addTestSource(simpleClassContent); + + await initializeServer(); + var action = await expectCodeActionWithTitle(simpleClassRefactorTitle); + var command = action.asCommand; + + // Resolve the command, which should add the outstanding form fields. + var resolvedCommand = await resolveCommand( + ExecuteCommandParams( + command: command.command, + arguments: command.arguments, + ), + ); + + // Resolve again, with an invalid value for URI. + expect(resolvedCommand.formFields, hasLength(1)); + var field = resolvedCommand.formFields!.single; + resolvedCommand = await resolveCommand( + InteractiveExecuteCommandParams( + command: resolvedCommand.command, + arguments: resolvedCommand.arguments, + formFields: resolvedCommand.formFields, + formAnswers: [field.answer('not a valid file uri')], + ), + ); + + // Check the field has a validation error and the current value is + // preserved. + expect(resolvedCommand.formFields, hasLength(1)); + field = resolvedCommand.formFields!.single; + expect(field.error, contains('valid file:// URI')); + expect( + resolvedCommand.formAnswers!.single, + field.answer('not a valid file uri'), + ); + } + + Future test_validates_notFileUri() async { + addTestSource(simpleClassContent); + + await initializeServer(); + var action = await expectCodeActionWithTitle(simpleClassRefactorTitle); + var command = action.asCommand; + + // Resolve the command, which should add the outstanding form fields. + var resolvedCommand = await resolveCommand( + ExecuteCommandParams( + command: command.command, + arguments: command.arguments, + ), + ); + + // Resolve again, with the wrong scheme for the URI. + expect(resolvedCommand.formFields, hasLength(1)); + var field = resolvedCommand.formFields!.single; + resolvedCommand = await resolveCommand( + InteractiveExecuteCommandParams( + command: resolvedCommand.command, + arguments: resolvedCommand.arguments, + formFields: resolvedCommand.formFields, + formAnswers: [field.answer('https://example.org')], + ), + ); + + // Check the field has a validation error and the current value is + // preserved. + expect(resolvedCommand.formFields, hasLength(1)); + field = resolvedCommand.formFields!.single; + expect(field.error, contains('valid file:// URI')); + expect( + resolvedCommand.formAnswers!.single, + field.answer('https://example.org'), + ); + } +} + +@reflectiveTest +class CommandResolveTest extends AbstractLspAnalysisServerTest { + Future test_returnsInputForUnknownCommand() async { + // Basic command that only includes the normal fields from + // ExecuteCommandParams. This ensures calling resolve() for commands that + // don't need form input will return the same result (with no formFields). + var command = InteractiveExecuteCommandParams( + command: 'my_unknown_command', + arguments: [1, 'two'], + ); + + await initialize(); + + var resolvedCommand = await resolveCommand(command); + + expect(resolvedCommand, command); + } +} diff --git a/pkg/analysis_server/test/lsp/commands/test_all.dart b/pkg/analysis_server/test/lsp/commands/test_all.dart index 729fd02b885..b812c84c0f3 100644 --- a/pkg/analysis_server/test/lsp/commands/test_all.dart +++ b/pkg/analysis_server/test/lsp/commands/test_all.dart @@ -6,10 +6,12 @@ import 'package:test_reflective_loader/test_reflective_loader.dart'; import 'apply_code_action_test.dart' as apply_code_action; import 'fix_all_in_workspace_test.dart' as fix_all_in_workspace; +import 'resolve_test.dart' as resolve; void main() { defineReflectiveSuite(() { apply_code_action.main(); fix_all_in_workspace.main(); + resolve.main(); }); } diff --git a/pkg/analysis_server/test/lsp/request_helpers_mixin.dart b/pkg/analysis_server/test/lsp/request_helpers_mixin.dart index 88d0bbcdafd..5e2e50985c6 100644 --- a/pkg/analysis_server/test/lsp/request_helpers_mixin.dart +++ b/pkg/analysis_server/test/lsp/request_helpers_mixin.dart @@ -988,6 +988,16 @@ mixin LspRequestHelpersMixin { ); } + Future resolveCommand( + ExecuteCommandParams command, + ) { + var request = makeRequest(CustomMethods.resolveCommand, command); + return expectSuccessfulResponseTo( + request, + InteractiveExecuteCommandParams.fromJson, + ); + } + Future resolveCompletion(CompletionItem item) { var request = makeRequest(Method.completionItem_resolve, item); return expectSuccessfulResponseTo(request, CompletionItem.fromJson); diff --git a/pkg/analysis_server/test/lsp/server_abstract.dart b/pkg/analysis_server/test/lsp/server_abstract.dart index 5028072edc7..c82e340bd58 100644 --- a/pkg/analysis_server/test/lsp/server_abstract.dart +++ b/pkg/analysis_server/test/lsp/server_abstract.dart @@ -786,6 +786,18 @@ mixin ClientCapabilitiesHelperMixin { }; } + void setSupportedInteractiveFormInputKinds(Set? inputTypes) { + const parentKey = 'interactiveResolve'; + const inputTypesKey = 'inputTypes'; + if (inputTypes != null) { + experimentalCapabilities[parentKey] = { + inputTypesKey: inputTypes.toList(), + }; + } else { + experimentalCapabilities.remove(parentKey); + } + } + void setSupportsWindowShowMessageRequest([bool supported = true]) { if (supported) { experimentalCapabilities['supportsWindowShowMessageRequest'] = true; diff --git a/pkg/analysis_server/test/src/services/refactoring/move_top_level_to_file_test.dart b/pkg/analysis_server/test/src/services/refactoring/move_top_level_to_file_test.dart index f5f588a3121..3215f11de77 100644 --- a/pkg/analysis_server/test/src/services/refactoring/move_top_level_to_file_test.dart +++ b/pkg/analysis_server/test/src/services/refactoring/move_top_level_to_file_test.dart @@ -9,35 +9,108 @@ import 'package:analyzer/src/test_utilities/test_code_format.dart'; import 'package:test/test.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; +import '../../../support/interactive_forms.dart'; +import '../../../utils/lsp_protocol_extensions.dart'; import 'refactoring_test_support.dart'; void main() { defineReflectiveSuite(() { - defineReflectiveTests(MoveTopLevelToFileTest); + defineReflectiveTests(OriginalInteractiveRefactorsMoveTopLevelToFileTest); + defineReflectiveTests(InteractiveFormsMoveTopLevelToFileTest); }); } +/// Tests running using the new Interactive Forms refactors +/// implementation. Extends [MoveTopLevelToFileTest] for basic tests and adds +/// protocol-specific tests. +/// +/// Tests in the base class do not excercise the input collecting mechanism, +/// they just verify the refactors using defaults behave the same. Tests in this +/// sub-class like [test_protocol_clientModifiedValues] test providing custom +/// (non-default) values. +/// +/// There are more tests verifying resolve behaviour in +/// `test/lsp/commands/resolve_test.dart` and tests verifying the Interactive +/// Forms helper classes in +/// `test/src/services/interactive_forms/interactive_forms_test.dart`. @reflectiveTest -class MoveTopLevelToFileTest extends RefactoringTest { +class InteractiveFormsMoveTopLevelToFileTest extends MoveTopLevelToFileTest + with InteractiveFormsTestMixin { + @override + Future initializeServer({ + bool experimentalOptInFlag = true, + // We default this to true for these tests, though it's false in the + // the super implementation. + bool experimentalInteractiveForms = true, + }) { + return super.initializeServer( + experimentalOptInFlag: experimentalOptInFlag, + experimentalInteractiveForms: experimentalInteractiveForms, + ); + } + + @override + void setUp() { + super.setUp(); + + setSupportedInteractiveFormInputKinds({'file'}); + } + + Future test_protocol_clientModifiedValues() async { + addTestSource(simpleClassContent); + + /// Filename to inject to replace default. + var newFilePath = join(projectFolderPath, 'lib', 'my_new_class.dart'); + var newFileUri = Uri.file(newFilePath); + + /// Expected new file content. + const expected = ''' +>>>>>>>>>> lib/main.dart empty +>>>>>>>>>> lib/my_new_class.dart created +class A {} +'''; + + await initializeServer(); + var action = await expectCodeActionWithTitle(simpleClassRefactorTitle); + var originalCommand = action.command!; + + var completedCommand = await completeInteractiveForm(originalCommand, { + 'destinationUri': newFileUri.toString(), + }); + + await verifyCommandEdits(completedCommand, expected); + } + + /// Since we have Interactive Forms enabled, we should not see the parameters + /// (which support the original Dart-specific interactive refactors) in the + /// returned action's data field. + Future test_protocol_doesNotAddParametersToData() async { + // data is on CodeAction so we need to support that, otherwise we'll get the + // Command version (which would just execute with the default value in the + // original system). + setSupportedCodeActionKinds([CodeActionKind.Refactor]); + + addTestSource(simpleClassContent); + + await initializeServer(); + var action = await expectCodeActionWithTitle(simpleClassRefactorTitle); + var actionLiteral = action.asCodeActionLiteral; + expect(actionLiteral.data, isNull); + } +} + +abstract class MoveTopLevelToFileTest extends RefactoringTest { /// Simple file content with a single class named 'A'. - static const simpleClassContent = ''' + final simpleClassContent = ''' class ^A {} '''; /// The title of the refactor when using [simpleClassContent]. - static const simpleClassRefactorTitle = "Move 'A' to file"; + final simpleClassRefactorTitle = "Move 'A' to file"; @override String get refactoringCommandId => MoveTopLevelToFile.commandName; - /// Replaces the "Save URI" argument in [action]. - void replaceSaveUriArgument(CodeAction action, Uri newFileUri) { - var arguments = getRefactorCommandArguments(action.command?.arguments); - // The filename is the first item we prompt for so is first in the - // arguments. - arguments[0] = newFileUri.toString(); - } - @override void setUp() { super.setUp(); @@ -1349,34 +1422,6 @@ class A {}<<<<<<<<<< await expectCodeActionWithTitle(simpleClassRefactorTitle); } - Future test_protocol_clientModifiedValues() async { - addTestSource(simpleClassContent); - - /// Filename to inject to replace default. - var newFilePath = join(projectFolderPath, 'lib', 'my_new_class.dart'); - var newFileUri = Uri.file(newFilePath); - - /// Expected new file content. - const expected = ''' ->>>>>>>>>> lib/main.dart empty ->>>>>>>>>> lib/my_new_class.dart created -class A {} -'''; - - await initializeServer(); - var action = await expectCodeActionWithTitle(simpleClassRefactorTitle); - // Replace the file URI argument with our custom path. - replaceSaveUriArgument(action, newFileUri); - await verifyCommandEdits(action.command!, expected); - } - - Future test_protocol_unavailable_withoutFileCreateSupport() async { - addTestSource(simpleClassContent); - setFileCreateSupport(false); - await initializeServer(); - await expectNoCodeActionWithTitle(simpleClassRefactorTitle); - } - Future test_sealedClass_enumImplements() async { var originalSource = ''' sealed class [!Either!] {} @@ -2027,6 +2072,13 @@ int variableToMove = 3; ); } + Future test_unavailable_withoutFileCreateSupport() async { + addTestSource(simpleClassContent); + setFileCreateSupport(false); + await initializeServer(); + await expectNoCodeActionWithTitle(simpleClassRefactorTitle); + } + Future _multipleDeclarations({ required String originalSource, required int count, @@ -2099,3 +2151,53 @@ ${code.code} ); } } + +/// Tests running using the original (Dart-specific) interactive refactors +/// implementation. Extends [MoveTopLevelToFileTest] for basic tests and add +/// protocol-specific tests. +@reflectiveTest +class OriginalInteractiveRefactorsMoveTopLevelToFileTest + extends MoveTopLevelToFileTest { + /// Replaces the "Save URI" argument in [action]. + void replaceSaveUriArgument(CodeAction action, Uri newFileUri) { + var arguments = getRefactorCommandArguments(action.command!.arguments); + // The filename is the first item we prompt for so is first in the + // arguments. + arguments[0] = newFileUri.toString(); + } + + Future test_protocol_addsParametersToData() async { + // data is on CodeAction so we need to support that, otherwise we'll get the + // Command version (which would just execute with the default value). + setSupportedCodeActionKinds([CodeActionKind.Refactor]); + + addTestSource(simpleClassContent); + + await initializeServer(); + var action = await expectCodeActionWithTitle(simpleClassRefactorTitle); + var actionLiteral = action.asCodeActionLiteral; + expect(actionLiteral.data, isNotNull); + expect(actionLiteral.data, contains('parameters')); + } + + Future test_protocol_clientModifiedValues() async { + addTestSource(simpleClassContent); + + /// Filename to inject to replace default. + var newFilePath = join(projectFolderPath, 'lib', 'my_new_class.dart'); + var newFileUri = Uri.file(newFilePath); + + /// Expected new file content. + const expected = ''' +>>>>>>>>>> lib/main.dart empty +>>>>>>>>>> lib/my_new_class.dart created +class A {} +'''; + + await initializeServer(); + var action = await expectCodeActionWithTitle(simpleClassRefactorTitle); + // Replace the file URI argument with our custom path. + replaceSaveUriArgument(action, newFileUri); + await verifyCommandEdits(action.command!, expected); + } +} diff --git a/pkg/analysis_server/test/support/interactive_forms.dart b/pkg/analysis_server/test/support/interactive_forms.dart index dc83f962d9a..027a37f8076 100644 --- a/pkg/analysis_server/test/support/interactive_forms.dart +++ b/pkg/analysis_server/test/support/interactive_forms.dart @@ -3,6 +3,65 @@ // BSD-style license that can be found in the LICENSE file. import 'package:language_server_protocol/protocol_custom_generated.dart'; +import 'package:language_server_protocol/protocol_generated.dart'; +import 'package:matcher/expect.dart'; + +import '../lsp/request_helpers_mixin.dart'; + +mixin InteractiveFormsTestMixin on LspRequestHelpersMixin { + /// A helper to complete an interactive form, taking the place of a user and + /// the client-side code. + /// + /// [command] is the original command that will be resolved. + /// + /// [answers] is a map of Field ID -> answer values that should be provided. + /// + /// Returns an updated command after fields have been answered that can be + /// executed. + Future completeInteractiveForm( + Command command, + Map answers, + ) async { + var interactiveCommand = InteractiveExecuteCommandParams( + command: command.command, + arguments: command.arguments, + ); + + // Perform an initial resolve to get the form. + interactiveCommand = await resolveCommand(interactiveCommand); + + // Expect at least some fields (we wouldn't have been called if none were + // expected). + expect(interactiveCommand.formFields, allOf(isNotNull, isNotEmpty)); + + // Ensure all answers we have are in the form. + expect( + interactiveCommand.formFields!.map((field) => field.id), + containsAll(answers.keys), + ); + + // Resolve again, using the answers we were given. + interactiveCommand = InteractiveExecuteCommandParams( + command: interactiveCommand.command, + arguments: interactiveCommand.arguments, + formFields: interactiveCommand.formFields, + formAnswers: answers.keys + .map((id) => FormAnswer(id: id, value: answers[id])) + .toList(), + ); + interactiveCommand = await resolveCommand(interactiveCommand); + + // Ensure the form is considered complete. + expect(interactiveCommand.formFields, isNull); + + // Return the updated command. + return Command( + title: command.title, + command: interactiveCommand.command, + arguments: interactiveCommand.arguments, + ); + } +} extension FormFieldExtension on FormField { /// Returns a [FormAnswer] for this field with the answer [value]. diff --git a/pkg/analysis_server/tool/lsp_spec/README.md b/pkg/analysis_server/tool/lsp_spec/README.md index 24c27b9391e..ca03b1ab43c 100644 --- a/pkg/analysis_server/tool/lsp_spec/README.md +++ b/pkg/analysis_server/tool/lsp_spec/README.md @@ -61,103 +61,103 @@ Below is a list of LSP methods and their implementation status. - Method: The LSP method name - Server: The method is supported by the Dart server. -- Plugins: This functionality can be extended by third party analyzer plugins. -| Method | Server | Plugins | Notes | -| - | - | - | - | -| initialize | ✅ | N/A | trace and other options NYI | -| initialized | ✅ | N/A | | -| shutdown | ✅ | N/A | supported but does nothing | -| exit | ✅ | N/A | | -| $/cancelRequest | ✅ | | | -| $/logTrace | | | | -| $/progress | | | | -| $/setTrace | | | | -| client/registerCapability | ✅ | ✅ | | -| client/unregisterCapability | ✅ | ✅ | | -| notebookDocument/* | | | | -| telemetry/event | | | | -| textDocument/codeAction (assists) | ✅ | ✅ | Only if the client advertises `codeActionLiteralSupport` with `Refactor` | -| textDocument/codeAction (fixAll) | ✅ | | | -| textDocument/codeAction (fixes) | ✅ | ✅ | Only if the client advertises `codeActionLiteralSupport` with `QuickFix` | -| textDocument/codeAction (organiseImports) | ✅ | | | -| textDocument/codeAction (refactors) | ✅ | | | -| textDocument/codeAction (sortMembers) | ✅ | | | -| codeAction/resolve | | | | -| textDocument/codeLens | ✅ | | | -| codeLens/resolve | | | | -| textDocument/completion | ✅ | ✅ | | -| completionItem/resolve | ✅ | | | -| textDocument/declaration | | | | -| textDocument/definition | ✅ | ✅ | | -| textDocument/diagnostic | | | | -| textDocument/didChange | ✅ | ✅ | | -| textDocument/didClose | ✅ | ✅ | | -| textDocument/didOpen | ✅ | ✅ | | -| textDocument/didSave | | | | -| textDocument/documentColor | ✅ | | | -| textDocument/colorPresentation | ✅ | | | -| textDocument/documentHighlight | ✅ | | | -| textDocument/documentLink | ✅ | | | -| documentLink/resolve | | | | -| textDocument/documentSymbol | ✅ | | | -| textDocument/foldingRange | ✅ | ✅ | | -| textDocument/formatting | ✅ | | | -| textDocument/onTypeFormatting | ✅ | | | -| textDocument/rangeFormatting | ✅ | | | -| textDocument/hover | ✅ | | | -| textDocument/implementation | ✅ | | | -| textDocument/inlayHint | ✅ | | | -| inlayHint/resolve | | | | -| textDocument/inlineValue | ✅ | | | -| textDocument/linkedEditingRange | | | | -| textDocument/moniker | | | | -| textDocument/prepareCallHierarchy | ✅ | | | -| callHierarchy/incomingCalls | ✅ | | | -| callHierarchy/outgoingCalls | ✅ | | | -| textDocument/prepareRename | ✅ | | | -| textDocument/rename | ✅ | | | -| textDocument/prepareTypeHierarchy | ✅ | | | -| typeHierarchy/subtypes | ✅ | | | -| typeHierarchy/supertypes | ✅ | | | -| textDocument/publishDiagnostics | ✅ | ✅ | | -| textDocument/references | ✅ | | | -| textDocument/selectionRange | ✅ | | | -| textDocument/semanticTokens/full | ✅ | ✅ | | -| textDocument/semanticTokens/full/delta | | | | -| textDocument/semanticTokens/range | ✅ | ✅ | | -| workspace/semanticTokens/refresh | | | | -| textDocument/signatureHelp | ✅ | | | -| textDocument/typeDefinition | ✅ | | | -| textDocument/willSave | | | | -| textDocument/willSaveWaitUntil | | | | -| window/logMessage | ✅ | | | -| window/showDocument | | | | -| window/showMessage | ✅ | | | -| window/showMessageRequest | | | | -| window/workDoneProgress/cancel | | | | -| window/workDoneProgress/create | ✅ | | | -| workspace/applyEdit | ✅ | | | -| workspace/codeLens/refresh | | | | -| workspace/configuration | ✅ | | | -| workspace/diagnostic | | | | -| workspace/diagnostic/refresh | | | | -| workspace/didChangeConfiguration | ✅ | | | -| workspace/didChangeWatchedFiles | | | unused, server does own watching | -| workspace/didChangeWorkspaceFolders | ✅ | ✅ | | -| workspace/didCreateFiles | | | | -| workspace/didDeleteFiles | | | | -| workspace/didRenameFiles | | | | -| workspace/executeCommand | ✅ | | | -| workspace/inlayHint/refresh | | | | -| workspace/inlineValue/refresh | | | | -| workspace/symbol | ✅ | | | -| workspaceSymbol/resolve | | | | -| workspace/willCreateFiles | | | | -| workspace/willDeleteFiles | | | | -| workspace/willRenameFiles | | | | -| workspace/willRenameFiles | ✅ | | | -| workspace/workspaceFolders | | | | +| Method | Server | Notes | +| - | - | - | +| initialize | ✅ | trace and other options NYI | +| initialized | ✅ | | +| shutdown | ✅ | supported but does nothing | +| exit | ✅ | | +| $/cancelRequest | ✅ | | +| $/logTrace | | | +| $/progress | | | +| $/setTrace | | | +| client/registerCapability | ✅ | | +| client/unregisterCapability | ✅ | | +| command/resolve | ✅ | A custom command used to support the experimental "Interactive Forms" feature | +| notebookDocument/* | | | +| telemetry/event | | | +| textDocument/codeAction (assists) | ✅ | Only if the client advertises `codeActionLiteralSupport` with `Refactor` | +| textDocument/codeAction (fixAll) | ✅ | | +| textDocument/codeAction (fixes) | ✅ | Only if the client advertises `codeActionLiteralSupport` with `QuickFix` | +| textDocument/codeAction (organiseImports) | ✅ | | +| textDocument/codeAction (refactors) | ✅ | | +| textDocument/codeAction (sortMembers) | ✅ | | +| codeAction/resolve | | | +| textDocument/codeLens | ✅ | | +| codeLens/resolve | | | +| textDocument/completion | ✅ | | +| completionItem/resolve | ✅ | | +| textDocument/declaration | | | +| textDocument/definition | ✅ | | +| textDocument/diagnostic | | | +| textDocument/didChange | ✅ | | +| textDocument/didClose | ✅ | | +| textDocument/didOpen | ✅ | | +| textDocument/didSave | | | +| textDocument/documentColor | ✅ | | +| textDocument/colorPresentation | ✅ | | +| textDocument/documentHighlight | ✅ | | +| textDocument/documentLink | ✅ | | +| documentLink/resolve | | | +| textDocument/documentSymbol | ✅ | | +| textDocument/foldingRange | ✅ | | +| textDocument/formatting | ✅ | | +| textDocument/onTypeFormatting | ✅ | | +| textDocument/rangeFormatting | ✅ | | +| textDocument/hover | ✅ | | +| textDocument/implementation | ✅ | | +| textDocument/inlayHint | ✅ | | +| inlayHint/resolve | | | +| textDocument/inlineValue | ✅ | | +| textDocument/linkedEditingRange | | | +| textDocument/moniker | | | +| textDocument/prepareCallHierarchy | ✅ | | +| callHierarchy/incomingCalls | ✅ | | +| callHierarchy/outgoingCalls | ✅ | | +| textDocument/prepareRename | ✅ | | +| textDocument/rename | ✅ | | +| textDocument/prepareTypeHierarchy | ✅ | | +| typeHierarchy/subtypes | ✅ | | +| typeHierarchy/supertypes | ✅ | | +| textDocument/publishDiagnostics | ✅ | | +| textDocument/references | ✅ | | +| textDocument/selectionRange | ✅ | | +| textDocument/semanticTokens/full | ✅ | | +| textDocument/semanticTokens/full/delta | | | +| textDocument/semanticTokens/range | ✅ | | +| workspace/semanticTokens/refresh | | | +| textDocument/signatureHelp | ✅ | | +| textDocument/typeDefinition | ✅ | | +| textDocument/willSave | | | +| textDocument/willSaveWaitUntil | | | +| window/logMessage | ✅ | | +| window/showDocument | | | +| window/showMessage | ✅ | | +| window/showMessageRequest | | | +| window/workDoneProgress/cancel | | | +| window/workDoneProgress/create | ✅ | | +| workspace/applyEdit | ✅ | | +| workspace/codeLens/refresh | | | +| workspace/configuration | ✅ | | +| workspace/diagnostic | | | +| workspace/diagnostic/refresh | | | +| workspace/didChangeConfiguration | ✅ | | +| workspace/didChangeWatchedFiles | | unused, server does own watching | +| workspace/didChangeWorkspaceFolders | ✅ | | +| workspace/didCreateFiles | | | +| workspace/didDeleteFiles | | | +| workspace/didRenameFiles | | | +| workspace/executeCommand | ✅ | | +| workspace/inlayHint/refresh | | | +| workspace/inlineValue/refresh | | | +| workspace/symbol | ✅ | | +| workspaceSymbol/resolve | | | +| workspace/willCreateFiles | | | +| workspace/willDeleteFiles | | | +| workspace/willRenameFiles | | | +| workspace/willRenameFiles | ✅ | | +| workspace/workspaceFolders | | | ## Custom Fields, Methods and Notifications diff --git a/pkg/analysis_server/tool/lsp_spec/custom/interactive_forms.dart b/pkg/analysis_server/tool/lsp_spec/custom/interactive_forms.dart index 001cf6b59ca..ee1cc11a163 100644 --- a/pkg/analysis_server/tool/lsp_spec/custom/interactive_forms.dart +++ b/pkg/analysis_server/tool/lsp_spec/custom/interactive_forms.dart @@ -5,7 +5,7 @@ import '../meta_model.dart'; import '../utils.dart'; -/// Classes that support for the new (Go-specified) interactive-refactors. +/// Classes that support for Interactive Forms. final interactiveFormClasses = [ // TODO(dantup): Generate this from a JSON metadata file if one is made in the // same format as the LSP metaModel file. diff --git a/pkg/analysis_server/tool/lsp_spec/generate_all.dart b/pkg/analysis_server/tool/lsp_spec/generate_all.dart index c887912f305..6021391f92d 100644 --- a/pkg/analysis_server/tool/lsp_spec/generate_all.dart +++ b/pkg/analysis_server/tool/lsp_spec/generate_all.dart @@ -321,7 +321,7 @@ List getCustomClasses() { // Support for the original (Dart-specific) interactive-refactors. ...interactiveRefactorsClasses, - // Support for the new (Go-specified) interactive-refactors. + // Support for Interactive Forms. ...interactiveFormClasses, ]; return customTypes;