blob: 14b3b1c3cb327cc58cf19e0a9b01b539442f6bf8 [file] [log] [blame]
// Copyright (c) 2019, 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 'dart:async';
import 'package:analysis_server/lsp_protocol/protocol.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/abstract_refactor.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/protocol_server.dart';
import 'package:meta/meta.dart';
class PerformRefactorCommandHandler extends AbstractRefactorCommandHandler
with LspHandlerHelperMixin {
/// A [Future] used by tests to allow inserting a delay between resolving
/// the initial unit and the refactor running.
@visibleForTesting
static Future<void>? delayAfterResolveForTests;
PerformRefactorCommandHandler(super.server);
@override
String get commandName => 'Perform Refactor';
@override
bool get recordsOwnAnalytics => true;
@override
FutureOr<ErrorOr<void>> execute(
String path,
String kind,
int offset,
int length,
Map<String, Object?>? options,
LspClientCapabilities clientCapabilities,
CancellationToken cancellationToken,
ProgressReporter reporter,
int? docVersion,
) async {
var actionName = 'dart.refactor.${kind.toLowerCase()}';
server.analyticsManager.executedCommand(actionName);
var result = await requireResolvedUnit(path);
if (delayAfterResolveForTests != null) {
await delayAfterResolveForTests;
}
return result.mapResult((result) async {
var refactoring = await getRefactoring(
RefactoringKind(kind), result, offset, length, options);
return refactoring.mapResult((refactoring) async {
// Don't include potential edits in refactorings until there is some UI
// for the user to control this.
refactoring.includePotential = false;
// If the token we were given is not cancelable, wrap it with one that
// is for the rest of this request as a future refactor may need to
// cancel this request.
var cancelableToken = cancellationToken.asCancelable();
manager.begin(cancelableToken);
try {
reporter.begin('Refactoring…');
var status = await refactoring.checkAllConditions();
if (status.hasError) {
// Show the error to the user but don't fail the request, as the
// LSP Client may show a failed request in a way that looks like a
// server error.
server.showErrorMessageToUser(status.message!);
return success(null);
}
if (cancelableToken.isCancellationRequested) {
return error(ErrorCodes.RequestCancelled, 'Request was cancelled');
}
var change = await refactoring.createChange();
if (cancelableToken.isCancellationRequested) {
return error(ErrorCodes.RequestCancelled, 'Request was cancelled');
}
if (change.edits.isEmpty) {
return success(null);
}
// If the file changed while we were validating and preparing the change,
// we should fail to avoid sending bad edits.
if (fileHasBeenModified(path, docVersion)) {
return fileModifiedError;
}
var edit = createWorkspaceEdit(server, clientCapabilities, change);
return await sendWorkspaceEditToClient(edit);
} finally {
manager.end(cancelableToken);
reporter.end();
}
});
});
}
}