blob: 646f9b66f698cf6166f0e462a556aa9383843aaf [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 'dart:async';
import 'package:analysis_server/src/protocol_server.dart'
show
CompletionSuggestion,
RuntimeCompletionExpression,
RuntimeCompletionVariable,
SourceEdit;
import 'package:analysis_server/src/provisional/completion/completion_core.dart';
import 'package:analysis_server/src/services/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:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/dart/analysis/driver.dart';
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
class RuntimeCompletionComputer {
final ResourceProvider resourceProvider;
final FileContentOverlay fileContentOverlay;
final AnalysisDriver analysisDriver;
final String code;
final int offset;
final String contextFile;
final int contextOffset;
final List<RuntimeCompletionVariable> variables;
final List<RuntimeCompletionExpression> expressions;
AnalysisSession session;
_Context context;
RuntimeCompletionComputer(
this.resourceProvider,
this.fileContentOverlay,
this.analysisDriver,
this.code,
this.offset,
this.contextFile,
this.contextOffset,
this.variables,
this.expressions);
Future<RuntimeCompletionResult> compute() async {
var pathContext = resourceProvider.pathContext;
var contextDir = pathContext.dirname(contextFile);
var targetPath = pathContext.join(contextDir, '_runtimeCompletion.dart');
// TODO(scheglov) Use variables.
await _initContext();
String baseTargetCode = r'''
library _runtimeCompletion;
''';
fileContentOverlay[targetPath] = baseTargetCode;
const codeMarker = '__code__';
// Build the target code that provides the same context.
var changeBuilder = new DartChangeBuilder(session);
int nextImportPrefixIndex = 0;
await changeBuilder.addFileEdit(targetPath, (builder) {
builder.addInsertion(baseTargetCode.length, (builder) {
builder.writeln('main() {');
// Write all visible local declarations.
for (var local in context.locals.values) {
builder.writeLocalVariableDeclaration(local.name, type: local.type);
builder.writeln();
}
// Write the marker to insert the code being completed.
builder.write(codeMarker);
// Finalize main().
builder.writeln(';');
builder.writeln('}');
});
}, importPrefixGenerator: (uri) => '__prefix${nextImportPrefixIndex++}');
// Compute the target code.
List<SourceEdit> targetEdits = changeBuilder.sourceChange.edits[0].edits;
String targetCode = SourceEdit.applySequence(baseTargetCode, targetEdits);
// Insert the code being completed.
int targetOffset = targetCode.indexOf(codeMarker) + offset;
targetCode = targetCode.replaceAll(codeMarker, code);
// Resolve the constructed target file.
AnalysisResult targetResult;
try {
fileContentOverlay[targetPath] = targetCode;
analysisDriver.changeFile(targetPath);
targetResult = await analysisDriver.getResult(targetPath);
} finally {
fileContentOverlay[targetPath] = null;
analysisDriver.changeFile(targetPath);
}
CompletionContributor contributor = new DartCompletionManager();
CompletionRequestImpl request = new CompletionRequestImpl(
targetResult,
targetOffset,
new CompletionPerformance(),
);
var suggestions = await contributor.computeSuggestions(request);
// TODO(scheglov) Add support for expressions.
var expressions = <RuntimeCompletionExpression>[];
return new RuntimeCompletionResult(expressions, suggestions);
}
Future<void> _initContext() async {
var contextResult = await analysisDriver.getResult(contextFile);
session = contextResult.session;
context = new _Context(contextResult.unit, contextOffset);
}
}
/// The result of performing runtime completion.
class RuntimeCompletionResult {
final List<RuntimeCompletionExpression> expressions;
final List<CompletionSuggestion> suggestions;
RuntimeCompletionResult(this.expressions, this.suggestions);
}
/// The context in which completion is performed.
class _Context {
final int contextOffset;
ClassElement enclosingClass;
Map<String, VariableElement> locals = {};
_Context(CompilationUnit unit, this.contextOffset) {
var node = new NodeLocator2(contextOffset).searchWithin(unit);
var enclosingClass = node.getAncestor((n) => n is ClassDeclaration);
if (enclosingClass is ClassDeclaration) {
this.enclosingClass = enclosingClass.element;
}
_appendLocals(node);
}
void _appendLocals(AstNode node) {
if (node is Block) {
for (var statement in node.statements) {
if (statement.offset > contextOffset) {
break;
}
if (statement is VariableDeclarationStatement) {
for (var variable in statement.variables.variables) {
VariableElement element = variable.element;
locals[element.name] ??= element;
}
}
}
} else if (node is ClassDeclaration) {
// TODO(scheglov) implement, maybe not locals anymore
return;
} else if (node is CompilationUnit) {
return;
} else if (node is FunctionDeclaration) {
_appendParameters(node.functionExpression.parameters);
} else if (node is MethodDeclaration) {
_appendParameters(node.parameters);
}
_appendLocals(node.parent);
}
void _appendParameters(FormalParameterList parameters) {
if (parameters != null) {
for (var parameter in parameters.parameters) {
VariableElement element = parameter.element;
locals[element.name] ??= element;
}
}
}
}