blob: 17a3b28f8e0ed877e267baae111f306245593775 [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 'package:analysis_server/lsp_protocol/protocol_generated.dart';
import 'package:analysis_server/lsp_protocol/protocol_special.dart';
import 'package:analysis_server/protocol/protocol_generated.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/lsp_analysis_server.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/search/type_hierarchy.dart';
class ImplementationHandler
extends MessageHandler<TextDocumentPositionParams, List<Location>> {
ImplementationHandler(LspAnalysisServer server) : super(server);
@override
Method get handlesMessage => Method.textDocument_implementation;
@override
LspJsonHandler<TextDocumentPositionParams> get jsonHandler =>
TextDocumentPositionParams.jsonHandler;
@override
Future<ErrorOr<List<Location>>> handle(
TextDocumentPositionParams params, CancellationToken token) async {
if (!isDartDocument(params.textDocument)) {
return success(const []);
}
final pos = params.position;
final path = pathOfDoc(params.textDocument);
final unit = await path.mapResult(requireResolvedUnit);
final offset = await unit.mapResult((unit) => toOffset(unit.lineInfo, pos));
return offset
.mapResult((offset) => _getImplementations(path.result, offset, token));
}
Future<ErrorOr<List<Location>>> _getImplementations(
String file, int offset, CancelableToken token) async {
final element = await server.getElementAtOffset(file, offset);
final computer = TypeHierarchyComputer(server.searchEngine, element);
if (token.isCancellationRequested) {
return cancelled();
}
final items = await computer.compute();
if (items == null || items.isEmpty) {
return success([]);
}
Iterable<TypeHierarchyItem> getDescendants(TypeHierarchyItem item) => item
.subclasses
?.map((i) => items[i])
?.followedBy(item.subclasses.expand((i) => getDescendants(items[i])));
// [TypeHierarchyComputer] returns the whole tree, but we specifically only
// want implementations (sub-classes). Find the referenced element and then
// recursively add its children.
var currentItem = items.firstWhere(
(item) {
final location =
item.memberElement?.location ?? item.classElement?.location;
return location.offset <= offset &&
location.offset + location.length >= offset;
},
// If we didn't find an item spanning our offset, we must've been at a
// call site so start everything from the root item.
orElse: () => items.first,
);
final isClass = currentItem.memberElement == null;
final locations = getDescendants(currentItem).where((item) {
// Filter based on type, so when searching for members we don't include
// any intermediate classes that don't have implementations for the
// method.
return isClass ? item.classElement != null : item.memberElement != null;
}).map((item) {
final elementLocation =
item.memberElement?.location ?? item.classElement?.location;
final lineInfo = server.getLineInfo(elementLocation.file);
return Location(
uri: Uri.file(elementLocation.file).toString(),
range:
toRange(lineInfo, elementLocation.offset, elementLocation.length),
);
}).toList();
return success(locations);
}
}