mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 15:21:54 +00:00
Add a refactor command
The command name is a bit unfortunate in that it isn't very readable for debugging purposes, but it has the advantage that there isn't any opportunity for it to get out of sync. That might be the wrong thing to optimize for. Let me know what you think. Change-Id: Iec5c2ad9703f51c7702d4d5094f0548b48fbaafd Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/254321 Reviewed-by: Danny Tuppeny <danny@tuppeny.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
parent
e139fb1527
commit
94d797ebee
|
@ -0,0 +1,86 @@
|
|||
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'package:analysis_server/lsp_protocol/protocol.dart';
|
||||
import 'package:analysis_server/src/lsp/constants.dart';
|
||||
import 'package:analysis_server/src/lsp/handlers/commands/simple_edit_handler.dart';
|
||||
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
|
||||
import 'package:analysis_server/src/lsp/mapping.dart';
|
||||
import 'package:analysis_server/src/lsp/progress.dart';
|
||||
import 'package:analysis_server/src/lsp/source_edits.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:analyzer/dart/analysis/results.dart';
|
||||
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
|
||||
|
||||
/// A command handler for any of the commands used to implement refactorings.
|
||||
class RefactorCommandHandler extends SimpleEditCommandHandler {
|
||||
@override
|
||||
final String commandName;
|
||||
|
||||
final ProducerGenerator generator;
|
||||
|
||||
RefactorCommandHandler(super.server, this.commandName, this.generator);
|
||||
|
||||
@override
|
||||
Future<ErrorOr<void>> handle(Map<String, Object?> parameters,
|
||||
ProgressReporter progress, CancellationToken cancellationToken) async {
|
||||
if (parameters['filePath'] is! String ||
|
||||
parameters['selectionOffset'] is! int ||
|
||||
parameters['selectionLength'] is! int ||
|
||||
parameters['arguments'] is! List<String>) {
|
||||
return ErrorOr.error(ResponseError(
|
||||
code: ServerErrorCodes.InvalidCommandArguments,
|
||||
message: 'Refactoring operations require 4 parameters: '
|
||||
'filePath: String, '
|
||||
'offset: int, '
|
||||
'length: int, '
|
||||
'arguments: Map<String, String>'));
|
||||
}
|
||||
var filePath = parameters['filePath'] as String;
|
||||
var offset = parameters['selectionOffset'] as int;
|
||||
var length = parameters['selectionLength'] as int;
|
||||
var arguments = parameters['arguments'] as List<String>;
|
||||
|
||||
final clientCapabilities = server.clientCapabilities;
|
||||
if (clientCapabilities == null) {
|
||||
// This should not happen unless a client misbehaves.
|
||||
return serverNotInitializedError;
|
||||
}
|
||||
|
||||
final result = await requireResolvedUnit(filePath);
|
||||
return result.mapResult((result) async {
|
||||
var context = RefactoringContext(
|
||||
server: server,
|
||||
resolvedResult: result,
|
||||
selectionOffset: offset,
|
||||
selectionLength: length);
|
||||
var producer = generator(context);
|
||||
var builder = ChangeBuilder(
|
||||
workspace: context.workspace, eol: context.utils.endOfLine);
|
||||
await producer.compute(arguments, builder);
|
||||
|
||||
var edits = builder.sourceChange.edits;
|
||||
if (edits.isEmpty) {
|
||||
return success(null);
|
||||
}
|
||||
|
||||
var fileEdits = <FileEditInformation>[];
|
||||
for (var edit in edits) {
|
||||
var path = edit.file;
|
||||
final fileResult = context.session.getFile(path);
|
||||
if (fileResult is! FileResult) {
|
||||
return ErrorOr.error(ResponseError(
|
||||
code: ServerErrorCodes.FileAnalysisFailed,
|
||||
message: 'Could not access "$path".'));
|
||||
}
|
||||
final docIdentifier = server.getVersionedDocumentIdentifier(path);
|
||||
fileEdits.add(FileEditInformation(
|
||||
docIdentifier, fileResult.lineInfo, edit.edits));
|
||||
}
|
||||
final workspaceEdit = toWorkspaceEdit(clientCapabilities, fileEdits);
|
||||
return sendWorkspaceEditToClient(workspaceEdit);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -7,14 +7,16 @@ import 'package:analysis_server/src/lsp/constants.dart';
|
|||
import 'package:analysis_server/src/lsp/handlers/commands/fix_all.dart';
|
||||
import 'package:analysis_server/src/lsp/handlers/commands/organize_imports.dart';
|
||||
import 'package:analysis_server/src/lsp/handlers/commands/perform_refactor.dart';
|
||||
import 'package:analysis_server/src/lsp/handlers/commands/refactor_command_handler.dart';
|
||||
import 'package:analysis_server/src/lsp/handlers/commands/send_workspace_edit.dart';
|
||||
import 'package:analysis_server/src/lsp/handlers/commands/sort_members.dart';
|
||||
import 'package:analysis_server/src/lsp/handlers/commands/validate_refactor.dart';
|
||||
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
|
||||
import 'package:analysis_server/src/lsp/progress.dart';
|
||||
import 'package:analysis_server/src/services/refactoring/framework/refactoring_processor.dart';
|
||||
|
||||
/// Handles workspace/executeCommand messages by delegating to a specific handler
|
||||
/// based on the command.
|
||||
/// Handles workspace/executeCommand messages by delegating to a specific
|
||||
/// handler based on the command.
|
||||
class ExecuteCommandHandler
|
||||
extends MessageHandler<ExecuteCommandParams, Object?> {
|
||||
final Map<String, CommandHandler> commandHandlers;
|
||||
|
@ -27,6 +29,9 @@ class ExecuteCommandHandler
|
|||
Commands.performRefactor: PerformRefactorCommandHandler(server),
|
||||
Commands.validateRefactor: ValidateRefactorCommandHandler(server),
|
||||
Commands.sendWorkspaceEdit: SendWorkspaceEditCommandHandler(server),
|
||||
// Add commands for each of the refactorings.
|
||||
for (var entry in RefactoringProcessor.generators.entries)
|
||||
entry.key: RefactorCommandHandler(server, entry.key, entry.value),
|
||||
};
|
||||
|
||||
@override
|
||||
|
|
|
@ -3,9 +3,12 @@
|
|||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'package:analysis_server/src/lsp/lsp_analysis_server.dart';
|
||||
import 'package:analysis_server/src/services/correction/change_workspace.dart';
|
||||
import 'package:analysis_server/src/services/correction/util.dart';
|
||||
import 'package:analyzer/dart/analysis/results.dart';
|
||||
import 'package:analyzer/dart/analysis/session.dart';
|
||||
import 'package:analyzer/src/dart/analysis/session_helper.dart';
|
||||
import 'package:analyzer_plugin/utilities/change_builder/change_workspace.dart';
|
||||
|
||||
/// The context in which a refactoring was requested.
|
||||
class RefactoringContext {
|
||||
|
@ -21,10 +24,16 @@ class RefactoringContext {
|
|||
/// The number of selected characters.
|
||||
final int selectionLength;
|
||||
|
||||
/// Utilities available to be used in the process of computing the edits.
|
||||
late final CorrectionUtils utils = CorrectionUtils(resolvedResult);
|
||||
|
||||
/// The helper used to efficiently access resolved units.
|
||||
late final AnalysisSessionHelper sessionHelper =
|
||||
AnalysisSessionHelper(session);
|
||||
|
||||
/// The change workspace associated with this refactoring.
|
||||
late final ChangeWorkspace workspace = DartChangeWorkspace([session]);
|
||||
|
||||
/// Initialize a newly created context based on the [resolvedResult].
|
||||
RefactoringContext({
|
||||
required this.server,
|
||||
|
|
|
@ -11,9 +11,9 @@ typedef ProducerGenerator = RefactoringProducer Function(RefactoringContext);
|
|||
|
||||
class RefactoringProcessor {
|
||||
/// A list of the generators used to produce refactorings.
|
||||
static const List<ProducerGenerator> generators = [
|
||||
// MoveTopLevelToFile.new,
|
||||
];
|
||||
static const Map<String, ProducerGenerator> generators = {
|
||||
// 'move_to_file': MoveTopLevelToFile.new,
|
||||
};
|
||||
|
||||
/// The context in which the refactorings could be applied.
|
||||
final RefactoringContext context;
|
||||
|
@ -24,8 +24,8 @@ class RefactoringProcessor {
|
|||
/// are available in the current context.
|
||||
Future<List<CodeAction>> compute() async {
|
||||
var refactorings = <CodeAction>[];
|
||||
for (var i = 0; i < generators.length; i++) {
|
||||
var generator = generators[i];
|
||||
for (var entry in RefactoringProcessor.generators.entries) {
|
||||
var generator = entry.value;
|
||||
var producer = generator(context);
|
||||
if (producer.isAvailable()) {
|
||||
refactorings.add(
|
||||
|
@ -33,13 +33,19 @@ class RefactoringProcessor {
|
|||
title: producer.title,
|
||||
kind: producer.kind,
|
||||
command: Command(
|
||||
command: producer.commandName,
|
||||
command: entry.key,
|
||||
title: producer.title,
|
||||
arguments: [
|
||||
{
|
||||
'filePath': context.resolvedResult.path,
|
||||
'selectionOffset': context.selectionOffset,
|
||||
'selectionLength': context.selectionLength,
|
||||
'arguments':
|
||||
producer.parameters.map((param) => param.defaultValue),
|
||||
}
|
||||
],
|
||||
),
|
||||
data: {
|
||||
'filePath': context.resolvedResult.path,
|
||||
'selectionOffset': context.selectionOffset,
|
||||
'selectionLength': context.selectionLength,
|
||||
'parameters': producer.parameters,
|
||||
}),
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue