From 59de9710ea2beb650b1f7aec008d9ff0f3662d93 Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Wed, 28 Mar 2018 04:26:36 +0000 Subject: [PATCH] Complete Flutter's setState() as a whole statement, with a closure, and the caret inside the closure. R=brianwilkerson@google.com, devoncarew@google.com Bug: https://github.com/flutter/flutter-intellij/issues/1916 Change-Id: I90b8c11c5435e1981fb334a4762bbe2131f3064f Reviewed-on: https://dart-review.googlesource.com/48526 Reviewed-by: Brian Wilkerson Commit-Queue: Konstantin Shcheglov --- .../dart/inherited_reference_contributor.dart | 64 ++++++++++++++--- .../completion/dart/suggestion_builder.dart | 9 +-- .../lib/src/utilities/flutter.dart | 5 ++ .../inherited_reference_contributor_test.dart | 70 +++++++++++++++++++ 4 files changed, 134 insertions(+), 14 deletions(-) diff --git a/pkg/analysis_server/lib/src/services/completion/dart/inherited_reference_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/inherited_reference_contributor.dart index 315b49c64f3cd..63d624c56537d 100644 --- a/pkg/analysis_server/lib/src/services/completion/dart/inherited_reference_contributor.dart +++ b/pkg/analysis_server/lib/src/services/completion/dart/inherited_reference_contributor.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart'; import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart'; +import 'package:analysis_server/src/utilities/flutter.dart' as flutter; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/standard_resolution_map.dart'; import 'package:analyzer/dart/element/element.dart'; @@ -81,16 +82,17 @@ class InheritedReferenceContributor extends DartCompletionContributor skipChildClass: skipChildClass); } - _addSuggestionsForType(InterfaceType type, OpType optype, + _addSuggestionsForType(InterfaceType type, DartCompletionRequest request, {bool isFunctionalArgument: false}) { + OpType opType = request.opType; if (!isFunctionalArgument) { for (PropertyAccessorElement elem in type.accessors) { if (elem.isGetter) { - if (optype.includeReturnValueSuggestions) { + if (opType.includeReturnValueSuggestions) { addSuggestion(elem); } } else { - if (optype.includeVoidReturnSuggestions) { + if (opType.includeVoidReturnSuggestions) { addSuggestion(elem); } } @@ -100,12 +102,13 @@ class InheritedReferenceContributor extends DartCompletionContributor if (elem.returnType == null) { addSuggestion(elem); } else if (!elem.returnType.isVoid) { - if (optype.includeReturnValueSuggestions) { + if (opType.includeReturnValueSuggestions) { addSuggestion(elem); } } else { - if (optype.includeVoidReturnSuggestions) { - addSuggestion(elem); + if (opType.includeVoidReturnSuggestions) { + CompletionSuggestion suggestion = addSuggestion(elem); + _updateFlutterSuggestions(request, elem, suggestion); } } } @@ -118,17 +121,58 @@ class InheritedReferenceContributor extends DartCompletionContributor kind = isFunctionalArgument ? CompletionSuggestionKind.IDENTIFIER : CompletionSuggestionKind.INVOCATION; - OpType optype = request.opType; - if (!skipChildClass) { - _addSuggestionsForType(classElement.type, optype, + _addSuggestionsForType(classElement.type, request, isFunctionalArgument: isFunctionalArgument); } for (InterfaceType type in classElement.allSupertypes) { - _addSuggestionsForType(type, optype, + _addSuggestionsForType(type, request, isFunctionalArgument: isFunctionalArgument); } return suggestions; } + + void _updateFlutterSuggestions(DartCompletionRequest request, Element element, + CompletionSuggestion suggestion) { + if (suggestion == null) { + return; + } + if (element is MethodElement && + element.name == 'setState' && + flutter.isExactState(element.enclosingElement)) { + // Find the line indentation. + String content = request.result.content; + int lineStartOffset = request.offset; + int notWhitespaceOffset = request.offset; + for (; lineStartOffset > 0; lineStartOffset--) { + var char = content.substring(lineStartOffset - 1, lineStartOffset); + if (char == '\n') { + break; + } + if (char != ' ' && char != '\t') { + notWhitespaceOffset = lineStartOffset - 1; + } + } + String indent = content.substring(lineStartOffset, notWhitespaceOffset); + + // Let the user know that we are going to insert a complete statement. + suggestion.displayText = 'setState(() {});'; + + // Build the completion and the selection offset. + var buffer = new StringBuffer(); + buffer.writeln('setState(() {'); + buffer.write('$indent '); + suggestion.selectionOffset = buffer.length; + buffer.writeln(); + buffer.write('$indent});'); + suggestion.completion = buffer.toString(); + + // There are no arguments to fill. + suggestion.parameterNames = null; + suggestion.parameterTypes = null; + suggestion.requiredParameterCount = null; + suggestion.hasNamedParameters = null; + } + } } diff --git a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart index 23248e45fe3ba..909345c81268e 100644 --- a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart +++ b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart @@ -144,13 +144,13 @@ abstract class ElementSuggestionBuilder { /** * Add a suggestion based upon the given element. */ - void addSuggestion(Element element, + CompletionSuggestion addSuggestion(Element element, {String prefix, int relevance: DART_RELEVANCE_DEFAULT, String elementCompletion}) { if (element.isPrivate) { if (element.library != containingLibrary) { - return; + return null; } } String completion = elementCompletion ?? element.displayName; @@ -162,7 +162,7 @@ abstract class ElementSuggestionBuilder { } } if (completion == null || completion.length <= 0) { - return; + return null; } CompletionSuggestion suggestion = createSuggestion(element, completion: completion, kind: kind, relevance: relevance); @@ -195,7 +195,7 @@ abstract class ElementSuggestionBuilder { typeParameters: getter.element.typeParameters, parameters: null, returnType: getter.returnType); - return; + return existingSuggestion; } // Cache lone getter/setter so that it can be paired @@ -206,6 +206,7 @@ abstract class ElementSuggestionBuilder { suggestions.add(suggestion); } } + return suggestion; } } diff --git a/pkg/analysis_server/lib/src/utilities/flutter.dart b/pkg/analysis_server/lib/src/utilities/flutter.dart index b5c82f1cb1eb9..bb06c0594ff47 100644 --- a/pkg/analysis_server/lib/src/utilities/flutter.dart +++ b/pkg/analysis_server/lib/src/utilities/flutter.dart @@ -259,6 +259,11 @@ bool isExactlyStatelessWidgetType(DartType type) { _isExactWidget(type.element, _STATELESS_WIDGET_NAME, _WIDGET_URI); } +/// Return `true` if the given [element] is the Flutter class `State`. +bool isExactState(ClassElement element) { + return _isExactWidget(element, _STATE_NAME, _WIDGET_URI); +} + /** * Return `true` if the given [type] is the Flutter class `Center`. */ diff --git a/pkg/analysis_server/test/services/completion/dart/inherited_reference_contributor_test.dart b/pkg/analysis_server/test/services/completion/dart/inherited_reference_contributor_test.dart index bd52c9e09c938..be6cb0a16a3e7 100644 --- a/pkg/analysis_server/test/services/completion/dart/inherited_reference_contributor_test.dart +++ b/pkg/analysis_server/test/services/completion/dart/inherited_reference_contributor_test.dart @@ -1,6 +1,7 @@ // 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. +import 'dart:async'; import 'package:analysis_server/src/protocol_server.dart'; import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart'; @@ -125,6 +126,42 @@ class A extends E implements I with M {a() {^}}'''); assertSuggestMethod('m2', 'M', 'int'); } + test_flutter_setState_hasPrefix() async { + var spaces_4 = ' ' * 4; + var spaces_6 = ' ' * 6; + await _check_flutter_setState( + ' setSt', + ''' +setState(() { +$spaces_6 +$spaces_4});''', + 20); + } + + test_flutter_setState_longPrefix() async { + var spaces_6 = ' ' * 6; + var spaces_8 = ' ' * 8; + await _check_flutter_setState( + ' setSt', + ''' +setState(() { +$spaces_8 +$spaces_6});''', + 22); + } + + test_flutter_setState_noPrefix() async { + var spaces_4 = ' ' * 4; + var spaces_6 = ' ' * 6; + await _check_flutter_setState( + ' ', + ''' +setState(() { +$spaces_6 +$spaces_4});''', + 20); + } + test_inherited() async { resolveSource('/testB.dart', ''' lib libB; @@ -604,4 +641,37 @@ class B extends A1 with A2 { assertNotSuggested('x2'); assertNotSuggested('y2'); } + + Future _check_flutter_setState( + String line, String completion, int selectionOffset) async { + addFlutterPackage(); + addTestSource(''' +import 'package:flutter/widgets.dart'; + +class TestWidget extends StatefulWidget { + @override + TestWidgetState createState() { + return new TestWidgetState(); + } +} + +class TestWidgetState extends State { + @override + Widget build(BuildContext context) { +$line^ + } +} +'''); + await computeSuggestions(); + CompletionSuggestion cs = + assertSuggest(completion, selectionOffset: selectionOffset); + expect(cs.selectionLength, 0); + + // It is an invocation, but we don't need any additional info for it. + // So, all parameter information is absent. + expect(cs.parameterNames, isNull); + expect(cs.parameterTypes, isNull); + expect(cs.requiredParameterCount, isNull); + expect(cs.hasNamedParameters, isNull); + } }