blob: 3c20785973269ba49864e9b0730c12d4e791ec8c [file] [log] [blame]
// Copyright (c) 2014, 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.
library services.completion.dart;
import 'dart:async';
import 'package:analysis_server/src/protocol.dart';
import 'package:analysis_server/src/services/completion/arglist_computer.dart';
import 'package:analysis_server/src/services/completion/combinator_computer.dart';
import 'package:analysis_server/src/services/completion/completion_manager.dart';
import 'package:analysis_server/src/services/completion/completion_target.dart';
import 'package:analysis_server/src/services/completion/dart_completion_cache.dart';
import 'package:analysis_server/src/services/completion/imported_computer.dart';
import 'package:analysis_server/src/services/completion/invocation_computer.dart';
import 'package:analysis_server/src/services/completion/keyword_computer.dart';
import 'package:analysis_server/src/services/completion/local_computer.dart';
import 'package:analysis_server/src/services/completion/optype.dart';
import 'package:analysis_server/src/services/search/search_engine.dart';
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/scanner.dart';
// Relevance highest to lowest
const int DART_RELEVANCE_HIGH = 2000;
const int DART_RELEVANCE_LOCAL_VARIABLE = 1059;
const int DART_RELEVANCE_PARAMETER = 1059;
const int DART_RELEVANCE_INHERITED_FIELD = 1058;
const int DART_RELEVANCE_LOCAL_FIELD = 1058;
const int DART_RELEVANCE_INHERITED_ACCESSOR = 1057;
const int DART_RELEVANCE_INHERITED_METHOD = 1057;
const int DART_RELEVANCE_LOCAL_ACCESSOR = 1057;
const int DART_RELEVANCE_LOCAL_METHOD = 1057;
const int DART_RELEVANCE_LOCAL_FUNCTION = 1056;
const int DART_RELEVANCE_LOCAL_TOP_LEVEL_VARIABLE = 1056;
const int DART_RELEVANCE_KEYWORD = 1055;
const int DART_RELEVANCE_DEFAULT = 1000;
const int DART_RELEVANCE_LOW = 500;
/**
* The base class for computing code completion suggestions.
*/
abstract class DartCompletionComputer {
/**
* Computes the initial set of [CompletionSuggestion]s based on
* the given completion context. The compilation unit and completion node
* in the given completion context may not be resolved.
* This method should execute quickly and not block waiting for any analysis.
* Returns `true` if the computer's work is complete
* or `false` if [computeFull] should be called to complete the work.
*/
bool computeFast(DartCompletionRequest request);
/**
* Computes the complete set of [CompletionSuggestion]s based on
* the given completion context. The compilation unit and completion node
* in the given completion context are resolved.
* Returns `true` if the receiver modified the list of suggestions.
*/
Future<bool> computeFull(DartCompletionRequest request);
}
/**
* Manages code completion for a given Dart file completion request.
*/
class DartCompletionManager extends CompletionManager {
final SearchEngine searchEngine;
final DartCompletionCache cache;
List<DartCompletionComputer> computers;
DartCompletionManager(AnalysisContext context, this.searchEngine,
Source source, this.cache, [this.computers])
: super(context, source) {
if (computers == null) {
computers = [
new KeywordComputer(),
new LocalComputer(),
new ArgListComputer(),
new CombinatorComputer(),
new ImportedComputer(),
new InvocationComputer()];
}
}
/**
* Create a new initialized Dart source completion manager
*/
factory DartCompletionManager.create(AnalysisContext context,
SearchEngine searchEngine, Source source) {
return new DartCompletionManager(
context,
searchEngine,
source,
new DartCompletionCache(context, source));
}
@override
Future<bool> computeCache() {
return waitForAnalysis().then((CompilationUnit unit) {
if (unit != null && !cache.isImportInfoCached(unit)) {
return cache.computeImportInfo(unit, searchEngine, true);
} else {
return new Future.value(false);
}
});
}
/**
* Compute suggestions based upon cached information only
* then send an initial response to the client.
* Return a list of computers for which [computeFull] should be called
*/
List<DartCompletionComputer> computeFast(DartCompletionRequest request) {
return request.performance.logElapseTime('computeFast', () {
CompilationUnit unit = context.parseCompilationUnit(source);
request.unit = unit;
request.node = new NodeLocator.con1(request.offset).searchWithin(unit);
request.target = new CompletionTarget.forOffset(unit, request.offset);
if (request.node == null) {
return [];
}
request.replacementOffset = request.offset;
request.replacementLength = 0;
var entity = request.target.entity;
Token token = entity is AstNode ? entity.beginToken : entity;
if (token != null &&
token.offset <= request.offset &&
(token.type == TokenType.KEYWORD || token.type == TokenType.IDENTIFIER)) {
request.replacementOffset = token.offset;
request.replacementLength = token.length;
}
List<DartCompletionComputer> todo = new List.from(computers);
todo.removeWhere((DartCompletionComputer c) {
return request.performance.logElapseTime(
'computeFast ${c.runtimeType}',
() {
return c.computeFast(request);
});
});
sendResults(request, todo.isEmpty);
return todo;
});
}
/**
* If there is remaining work to be done, then wait for the unit to be
* resolved and request that each remaining computer finish their work.
* Return a [Future] that completes when the last notification has been sent.
*/
Future computeFull(DartCompletionRequest request,
List<DartCompletionComputer> todo) {
request.performance.logStartTime('waitForAnalysis');
return waitForAnalysis().then((CompilationUnit unit) {
if (controller.isClosed) {
return;
}
request.performance.logElapseTime('waitForAnalysis');
if (unit == null) {
sendResults(request, true);
return;
}
request.performance.logElapseTime('computeFull', () {
request.unit = unit;
request.node = new NodeLocator.con1(request.offset).searchWithin(unit);
// TODO(paulberry): Do we need to invoke _ReplacementOffsetBuilder
// again?
request.target = new CompletionTarget.forOffset(unit, request.offset);
int count = todo.length;
todo.forEach((DartCompletionComputer c) {
String name = c.runtimeType.toString();
String completeTag = 'computeFull $name complete';
request.performance.logStartTime(completeTag);
request.performance.logElapseTime('computeFull $name', () {
c.computeFull(request).then((bool changed) {
request.performance.logElapseTime(completeTag);
bool last = --count == 0;
if (changed || last) {
sendResults(request, last);
}
});
});
});
});
});
}
@override
void computeSuggestions(CompletionRequest completionRequest) {
DartCompletionRequest request = new DartCompletionRequest(
context,
searchEngine,
source,
completionRequest.offset,
cache,
completionRequest.performance);
request.performance.logElapseTime('compute', () {
List<DartCompletionComputer> todo = computeFast(request);
if (!todo.isEmpty) {
computeFull(request, todo);
}
});
}
/**
* Send the current list of suggestions to the client.
*/
void sendResults(DartCompletionRequest request, bool last) {
if (controller == null || controller.isClosed) {
return;
}
controller.add(
new CompletionResult(
request.replacementOffset,
request.replacementLength,
request.suggestions,
last));
if (last) {
controller.close();
}
}
/**
* Return a future that either (a) completes with the resolved compilation
* unit when analysis is complete, or (b) completes with null if the
* compilation unit is never going to be resolved.
*/
Future<CompilationUnit> waitForAnalysis() {
List<Source> libraries = context.getLibrariesContaining(source);
assert(libraries != null);
if (libraries.length == 0) {
return new Future.value(null);
}
Source libSource = libraries[0];
assert(libSource != null);
return context.computeResolvedCompilationUnitAsync(
source,
libSource).catchError((_) {
// This source file is not scheduled for analysis, so a resolved
// compilation unit is never going to get computed.
return null;
}, test: (e) => e is AnalysisNotScheduledError);
}
}
/**
* The context in which the completion is requested.
*/
class DartCompletionRequest extends CompletionRequest {
/**
* The analysis context in which the completion is requested.
*/
final AnalysisContext context;
/**
* The search engine for use when building suggestions.
*/
final SearchEngine searchEngine;
/**
* The source in which the completion is requested.
*/
final Source source;
/**
* Cached information from a prior code completion operation.
*/
final DartCompletionCache cache;
/**
* The compilation unit in which the completion was requested. This unit
* may or may not be resolved when [DartCompletionComputer.computeFast]
* is called but is resolved when [DartCompletionComputer.computeFull].
*/
CompilationUnit unit;
/**
* The node in which the completion occurred. This node
* may or may not be resolved when [DartCompletionComputer.computeFast]
* is called but is resolved when [DartCompletionComputer.computeFull].
*/
AstNode node;
/**
* The completion target. This determines what part of the parse tree
* will receive the newly inserted text.
*
* TODO(paulberry) gradually transition code over to using this rather than
* [node].
*/
CompletionTarget target;
/**
* Information about the types of suggestions that should be included.
*/
OpType _optype;
/**
* The offset of the start of the text to be replaced.
* This will be different than the offset used to request the completion
* suggestions if there was a portion of an identifier before the original
* offset. In particular, the replacementOffset will be the offset of the
* beginning of said identifier.
*/
int replacementOffset;
/**
* The length of the text to be replaced if the remainder of the identifier
* containing the cursor is to be replaced when the suggestion is applied
* (that is, the number of characters in the existing identifier).
*/
int replacementLength;
/**
* The list of suggestions to be sent to the client.
*/
final List<CompletionSuggestion> suggestions = <CompletionSuggestion>[];
DartCompletionRequest(this.context, this.searchEngine, this.source,
int offset, this.cache, CompletionPerformance performance)
: super(offset, performance);
/**
* Return the original text from the [replacementOffset] to the [offset]
* that can be used to filter the suggestions on the server side.
*/
String get filterText {
return context.getContents(
source).data.substring(replacementOffset, offset);
}
/**
* Information about the types of suggestions that should be included.
* The [target] must be set first.
*/
OpType get optype {
if (_optype == null) {
_optype = new OpType.forCompletion(target, offset);
}
return _optype;
}
}