blob: 570ccb61c026c314146f89bce7e0de398c62c429 [file] [log] [blame]
// 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 'dart:async';
import 'package:analysis_server/protocol/protocol.dart';
import 'package:analysis_server/protocol/protocol_generated.dart';
import 'package:analysis_server/src/domains/completion/available_suggestions.dart';
import 'package:analysis_server/src/handler/legacy/completion_get_suggestions2.dart';
import 'package:analysis_server/src/provisional/completion/completion_core.dart';
import 'package:analysis_server/src/services/completion/completion_performance.dart';
import 'package:analysis_server/src/services/completion/dart/completion_manager.dart';
import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart';
import 'package:analyzer/src/util/performance/operation_performance.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
/// The handler for the `completion.getSuggestions` request.
class CompletionGetSuggestionsHandler extends CompletionGetSuggestions2Handler {
/// Initialize a newly created handler to be able to service requests for the
/// [server].
CompletionGetSuggestionsHandler(
super.server, super.request, super.cancellationToken);
@override
Future<void> handle() async {
if (completionIsDisabled) {
return;
}
var requestLatency = request.timeSinceRequest;
var budget = CompletionBudget(server.completionState.budgetDuration);
// extract and validate params
var params = CompletionGetSuggestionsParams.fromRequest(request);
var file = params.file;
var offset = params.offset;
if (server.sendResponseErrorIfInvalidFilePath(request, file)) {
return;
}
var performance = OperationPerformanceImpl('<root>');
performance.runAsync(
'request',
(performance) async {
if (file.endsWith('.yaml')) {
// Return the response without results.
var completionId =
(server.completionState.nextCompletionId++).toString();
server.sendResponse(CompletionGetSuggestionsResult(completionId)
.toResponse(request.id));
// Send a notification with results.
final suggestions = computeYamlSuggestions(file, offset);
sendCompletionNotification(
completionId,
suggestions.replacementOffset,
suggestions.replacementLength,
suggestions.suggestions,
null,
null,
null,
null,
);
return;
} else if (!file.endsWith('.dart')) {
// Return the response without results.
var completionId =
(server.completionState.nextCompletionId++).toString();
server.sendResponse(CompletionGetSuggestionsResult(completionId)
.toResponse(request.id));
// Send a notification with results.
sendCompletionNotification(
completionId, offset, 0, [], null, null, null, null);
return;
}
var resolvedUnit = await server.getResolvedUnit(file);
if (resolvedUnit == null) {
server.sendResponse(Response.fileNotAnalyzed(request, file));
return;
}
server.requestStatistics?.addItemTimeNow(request, 'resolvedUnit');
if (offset < 0 || offset > resolvedUnit.content.length) {
server.sendResponse(Response.invalidParameter(
request,
'params.offset',
'Expected offset between 0 and source length inclusive,'
' but found $offset'));
return;
}
final completionPerformance = CompletionPerformance(
performance: performance,
path: file,
requestLatency: requestLatency,
content: resolvedUnit.content,
offset: offset,
);
server.recentPerformance.completion.add(completionPerformance);
var declarationsTracker = server.declarationsTracker;
if (declarationsTracker == null) {
server.sendResponse(Response.unsupportedFeature(
request.id, 'Completion is not enabled.'));
return;
}
var completionRequest = DartCompletionRequest.forResolvedUnit(
resolvedUnit: resolvedUnit,
offset: offset,
dartdocDirectiveInfo: server.getDartdocDirectiveInfoFor(
resolvedUnit,
),
documentationCache: server.getDocumentationCacheFor(resolvedUnit),
);
var completionId =
(server.completionState.nextCompletionId++).toString();
setNewRequest(completionRequest);
// initial response without results
server.sendResponse(CompletionGetSuggestionsResult(completionId)
.toResponse(request.id));
// If the client opted into using available suggestion sets,
// create the kinds set, so signal the completion manager about opt-in.
Set<ElementKind>? includedElementKinds;
Set<String>? includedElementNames;
List<IncludedSuggestionRelevanceTag>? includedSuggestionRelevanceTags;
if (server.completionState.subscriptions
.contains(CompletionService.AVAILABLE_SUGGESTION_SETS)) {
includedElementKinds = <ElementKind>{};
includedElementNames = <String>{};
includedSuggestionRelevanceTags = <IncludedSuggestionRelevanceTag>[];
}
// Compute suggestions in the background
try {
var suggestionBuilders = <CompletionSuggestionBuilder>[];
try {
suggestionBuilders = await computeSuggestions(
budget: budget,
performance: performance,
request: completionRequest,
includedElementKinds: includedElementKinds,
includedElementNames: includedElementNames,
includedSuggestionRelevanceTags: includedSuggestionRelevanceTags,
);
} on AbortCompletion {
// Continue with empty suggestions list.
}
String? libraryFile;
var includedSuggestionSets = <IncludedSuggestionSet>[];
if (includedElementKinds != null && includedElementNames != null) {
libraryFile = resolvedUnit.libraryElement.source.fullName;
server.sendNotification(
createExistingImportsNotification(resolvedUnit),
);
computeIncludedSetList(
declarationsTracker,
completionRequest,
includedSuggestionSets,
includedElementNames,
);
}
const SEND_NOTIFICATION_TAG = 'send notification';
performance.run(SEND_NOTIFICATION_TAG, (_) {
sendCompletionNotification(
completionId,
completionRequest.replacementOffset,
completionRequest.replacementLength,
suggestionBuilders.map((e) => e.build()).toList(),
libraryFile,
includedSuggestionSets,
includedElementKinds?.toList(),
includedSuggestionRelevanceTags,
);
});
completionPerformance.computedSuggestionCount =
suggestionBuilders.length;
completionPerformance.transmittedSuggestionCount =
suggestionBuilders.length;
} finally {
_ifMatchesRequestClear(completionRequest);
}
},
);
}
void _ifMatchesRequestClear(DartCompletionRequest request) {
if (server.completionState.currentRequest == request) {
server.completionState.currentRequest = null;
}
}
}