Index: pkg/analysis_server/lib/src/services/completion/dart/arglist_contributor.dart |
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/arglist_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/arglist_contributor.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b4a6aed91064c16b618fa547f893038d3426ee02 |
--- /dev/null |
+++ b/pkg/analysis_server/lib/src/services/completion/dart/arglist_contributor.dart |
@@ -0,0 +1,244 @@ |
+// 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.contributor.dart.arglist; |
+ |
+import 'dart:async'; |
+ |
+import 'package:analysis_server/src/protocol_server.dart' |
+ hide Element, ElementKind; |
+import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart'; |
+import 'package:analyzer/src/generated/ast.dart'; |
+import 'package:analyzer/src/generated/element.dart'; |
+import 'package:analyzer/src/generated/utilities_dart.dart'; |
+ |
+/** |
+ * Determine the number of arguments. |
+ */ |
+int _argCount(DartCompletionRequest request) { |
+ AstNode node = request.target.containingNode; |
+ if (node is ArgumentList) { |
+ return node.arguments.length; |
+ } |
+ return 0; |
+} |
+ |
+String _getParamType(ParameterElement param) { |
+ DartType type = param.type; |
+ if (type != null) { |
+ return type.displayName; |
+ } |
+ return 'dynamic'; |
+} |
+ |
+/** |
+ * If the containing [node] is an argument list |
+ * or named expression in an argument list |
+ * then return the simple identifier for the method, constructor, or annotation |
+ * to which the argument list is associated |
+ */ |
+SimpleIdentifier _getTargetId(AstNode node) { |
+ if (node is NamedExpression) { |
+ return _getTargetId(node.parent); |
+ } |
+ if (node is ArgumentList) { |
+ AstNode parent = node.parent; |
+ if (parent is MethodInvocation) { |
+ return parent.methodName; |
+ } |
+ if (parent is InstanceCreationExpression) { |
+ ConstructorName constructorName = parent.constructorName; |
+ if (constructorName != null) { |
+ if (constructorName.name != null) { |
+ return constructorName.name; |
+ } |
+ Identifier typeName = constructorName.type.name; |
+ if (typeName is SimpleIdentifier) { |
+ return typeName; |
+ } |
+ if (typeName is PrefixedIdentifier) { |
+ return typeName.identifier; |
+ } |
+ } |
+ } |
+ if (parent is Annotation) { |
+ return parent.constructorName ?? parent.name; |
+ } |
+ } |
+ return null; |
+} |
+ |
+/** |
+ * Determine if the completion target is at the end of the list of arguments. |
+ */ |
+bool _isAppendingToArgList(DartCompletionRequest request) { |
+ AstNode node = request.target.containingNode; |
+ if (node is ArgumentList) { |
+ var entity = request.target.entity; |
+ if (entity == node.rightParenthesis) { |
+ return true; |
+ } |
+ if (node.arguments.length > 0 && node.arguments.last == entity) { |
+ return entity is SimpleIdentifier; |
+ } |
+ } |
+ return false; |
+} |
+ |
+/** |
+ * Determine if the completion target is an emtpy argument list. |
+ */ |
+bool _isEmptyArgList(DartCompletionRequest request) { |
+ AstNode node = request.target.containingNode; |
+ return node is ArgumentList && |
+ node.leftParenthesis.next == node.rightParenthesis; |
+} |
+ |
+/** |
+ * Return a collection of currently specified named arguments |
+ */ |
+Iterable<String> _namedArgs(DartCompletionRequest request) { |
+ AstNode node = request.target.containingNode; |
+ List<String> namedArgs = new List<String>(); |
+ if (node is ArgumentList) { |
+ for (Expression arg in node.arguments) { |
+ if (arg is NamedExpression) { |
+ namedArgs.add(arg.name.label.name); |
+ } |
+ } |
+ } |
+ return namedArgs; |
+} |
+ |
+/** |
+ * A contributor for calculating `completion.getSuggestions` request results |
+ * when the cursor position is inside the arguments to a method call. |
+ */ |
+class ArgListContributor extends DartCompletionContributor { |
+ DartCompletionRequest request; |
+ List<CompletionSuggestion> suggestions; |
+ |
+ @override |
+ Future<List<CompletionSuggestion>> computeSuggestions( |
+ DartCompletionRequest request) async { |
+ this.request = request; |
+ this.suggestions = <CompletionSuggestion>[]; |
+ |
+ // Determine if the target is in an argument list |
+ // for a method or a constructor or an annotation |
+ // and resolve the identifier |
+ SimpleIdentifier targetId = _getTargetId(request.target.containingNode); |
+ if (targetId == null) { |
+ return EMPTY_LIST; |
+ } |
+ |
+ // Resolve the target expression to determine the arguments |
+ await request.resolveIdentifier(targetId); |
+ // Gracefully degrade if the element could not be resolved |
+ // e.g. target changed, completion aborted |
+ targetId = _getTargetId(request.target.containingNode); |
+ if (targetId == null) { |
+ return EMPTY_LIST; |
+ } |
+ Element elem = targetId.bestElement; |
+ if (elem == null) { |
+ return EMPTY_LIST; |
+ } |
+ |
+ // Generate argument list suggestion based upon the type of element |
+ if (elem is ClassElement) { |
+ for (ConstructorElement constructor in elem.constructors) { |
+ if (!constructor.isFactory) { |
+ _addSuggestions(constructor.parameters); |
+ return suggestions; |
+ } |
+ } |
+ } |
+ if (elem is ConstructorElement) { |
+ _addSuggestions(elem.parameters); |
+ return suggestions; |
+ } |
+ if (elem is FunctionElement) { |
+ _addSuggestions(elem.parameters); |
+ return suggestions; |
+ } |
+ if (elem is MethodElement) { |
+ _addSuggestions(elem.parameters); |
+ return suggestions; |
+ } |
+ |
+ print('${elem.runtimeType} :: $elem'); |
+ return EMPTY_LIST; |
+ } |
+ |
+ void _addArgListSuggestion(Iterable<ParameterElement> requiredParam) { |
+ StringBuffer completion = new StringBuffer('('); |
+ List<String> paramNames = new List<String>(); |
+ List<String> paramTypes = new List<String>(); |
+ for (ParameterElement param in requiredParam) { |
+ String name = param.name; |
+ if (name != null && name.length > 0) { |
+ if (completion.length > 1) { |
+ completion.write(', '); |
+ } |
+ completion.write(name); |
+ paramNames.add(name); |
+ paramTypes.add(_getParamType(param)); |
+ } |
+ } |
+ completion.write(')'); |
+ CompletionSuggestion suggestion = new CompletionSuggestion( |
+ CompletionSuggestionKind.ARGUMENT_LIST, |
+ DART_RELEVANCE_HIGH, |
+ completion.toString(), |
+ completion.length, |
+ 0, |
+ false, |
+ false); |
+ suggestion.parameterNames = paramNames; |
+ suggestion.parameterTypes = paramTypes; |
+ suggestions.add(suggestion); |
+ } |
+ |
+ void _addDefaultParamSuggestions(Iterable<ParameterElement> parameters) { |
+ Iterable<String> namedArgs = _namedArgs(request); |
+ for (ParameterElement param in parameters) { |
+ if (param.parameterKind == ParameterKind.NAMED) { |
+ _addNamedParameterSuggestion(request, namedArgs, param.name); |
+ } |
+ } |
+ } |
+ |
+ void _addNamedParameterSuggestion( |
+ DartCompletionRequest request, List<String> namedArgs, String name) { |
+ if (name != null && name.length > 0 && !namedArgs.contains(name)) { |
+ suggestions.add(new CompletionSuggestion( |
+ CompletionSuggestionKind.NAMED_ARGUMENT, |
+ DART_RELEVANCE_NAMED_PARAMETER, |
+ '$name: ', |
+ name.length + 2, |
+ 0, |
+ false, |
+ false)); |
+ } |
+ } |
+ |
+ void _addSuggestions(Iterable<ParameterElement> parameters) { |
+ if (parameters == null || parameters.length == 0) { |
+ return; |
+ } |
+ Iterable<ParameterElement> requiredParam = parameters.where( |
+ (ParameterElement p) => p.parameterKind == ParameterKind.REQUIRED); |
+ int requiredCount = requiredParam.length; |
+ if (requiredCount > 0 && _isEmptyArgList(request)) { |
+ _addArgListSuggestion(requiredParam); |
+ return; |
+ } |
+ if (_isAppendingToArgList(request)) { |
+ if (requiredCount == 0 || requiredCount < _argCount(request)) { |
+ _addDefaultParamSuggestions(parameters); |
+ } |
+ } |
+ } |
+} |