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;