blob: 44c6e9cf998ba322d2dbc75f4ad41539f9a5b07a [file] [log] [blame]
// Copyright (c) 2018, 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/error_or.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/source_edits.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/error/error.dart' as engine;
import 'package:analyzer/src/dart/scanner/scanner.dart' as engine;
import 'package:analyzer/src/generated/parser.dart' as engine;
import 'package:analyzer_plugin/protocol/protocol_common.dart';
abstract class SimpleEditCommandHandler
extends CommandHandler<ExecuteCommandParams, Object> {
SimpleEditCommandHandler(super.server);
String get commandName;
bool hasScanParseErrors(List<engine.AnalysisError> errors) {
return errors.any((error) =>
error.errorCode is engine.ScannerErrorCode ||
error.errorCode is engine.ParserErrorCode);
}
Future<ErrorOr<void>> sendSourceEditsToClient(
OptionalVersionedTextDocumentIdentifier docIdentifier,
CompilationUnit unit,
List<SourceEdit> edits,
) async {
// If there are no edits to apply, just complete the command without going
// back to the client.
if (edits.isEmpty) {
return success(null);
}
// Use the editor capabilities, since we're building edits to send to the
// editor regardless of who called us.
var clientCapabilities = server.editorClientCapabilities;
if (clientCapabilities == null) {
return serverNotInitializedError;
}
var lineInfo = unit.lineInfo;
var workspaceEdit = toWorkspaceEdit(
clientCapabilities,
[
FileEditInformation(
docIdentifier,
lineInfo,
edits,
// New files are not supported from raw source edits. This is used
// only for Organize Imports / Sort Members which do not produce new
// files.
newFile: false,
)
],
);
return sendWorkspaceEditToClient(workspaceEdit);
}
Future<ErrorOr<void>> sendWorkspaceEditToClient(
WorkspaceEdit workspaceEdit) async {
// Send the edit to the client via a applyEdit request (this is a request
// from server -> client and the client will provide a response).
var editResponse = await server.sendRequest(Method.workspace_applyEdit,
ApplyWorkspaceEditParams(label: commandName, edit: workspaceEdit));
if (editResponse.error != null) {
return error(
ServerErrorCodes.ClientFailedToApplyEdit,
'Client failed to apply workspace edit for $commandName',
editResponse.error.toString(),
);
}
// Now respond to this command request telling the client whether it was
// successful (since the client doesn't know that the workspace edit it was
// sent - and may have failed to apply - was related to this command
// execution).
// We need to fromJson to convert the JSON map to the real types.
var editResponseResult = ApplyWorkspaceEditResult.fromJson(
editResponse.result as Map<String, Object?>);
var ApplyWorkspaceEditResult(:applied, :failureReason) = editResponseResult;
if (applied) {
return success(null);
} else if (failureReason == null) {
// If the user cancels a previewed refactoring, we will get applied=false
// but no failure reason. In this case, we do not want to reject this as
// an error.
return success(null);
} else {
return error(
ServerErrorCodes.ClientFailedToApplyEdit,
'Client failed to apply workspace edit for $commandName '
'(reason: $failureReason)',
workspaceEdit.toString(),
);
}
}
}