| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library services.completion.computer.dart.keyword; | 5 library services.completion.computer.dart.keyword; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 | 8 |
| 9 import 'package:analysis_server/src/protocol.dart'; | 9 import 'package:analysis_server/src/protocol.dart'; |
| 10 import 'package:analysis_server/src/services/completion/dart_completion_manager.
dart'; | 10 import 'package:analysis_server/src/services/completion/dart_completion_manager.
dart'; |
| 11 import 'package:analyzer/src/generated/ast.dart'; | 11 import 'package:analyzer/src/generated/ast.dart'; |
| 12 import 'package:analyzer/src/generated/scanner.dart'; | 12 import 'package:analyzer/src/generated/scanner.dart'; |
| 13 | 13 |
| 14 /** | 14 /** |
| 15 * A computer for calculating `completion.getSuggestions` request results | 15 * A computer for calculating `completion.getSuggestions` request results |
| 16 * for the local library in which the completion is requested. | 16 * for the local library in which the completion is requested. |
| 17 */ | 17 */ |
| 18 class KeywordComputer extends DartCompletionComputer { | 18 class KeywordComputer extends DartCompletionComputer { |
| 19 @override | 19 @override |
| 20 bool computeFast(DartCompletionRequest request) { | 20 bool computeFast(DartCompletionRequest request) { |
| 21 request.node.accept(new _KeywordVisitor(request)); | 21 request.target.containingNode.accept(new _KeywordVisitor(request)); |
| 22 return true; | 22 return true; |
| 23 } | 23 } |
| 24 | 24 |
| 25 @override | 25 @override |
| 26 Future<bool> computeFull(DartCompletionRequest request) { | 26 Future<bool> computeFull(DartCompletionRequest request) { |
| 27 return new Future.value(false); | 27 return new Future.value(false); |
| 28 } | 28 } |
| 29 } | 29 } |
| 30 | 30 |
| 31 /** | 31 /** |
| 32 * A vistor for generating keyword suggestions. | 32 * A vistor for generating keyword suggestions. |
| 33 */ | 33 */ |
| 34 class _KeywordVisitor extends GeneralizingAstVisitor { | 34 class _KeywordVisitor extends GeneralizingAstVisitor { |
| 35 final DartCompletionRequest request; | 35 final DartCompletionRequest request; |
| 36 final Object entity; |
| 36 | 37 |
| 37 /** | 38 _KeywordVisitor(DartCompletionRequest request) |
| 38 * The identifier visited or `null` if not visited. | 39 : this.request = request, |
| 39 */ | 40 this.entity = request.target.entity; |
| 40 SimpleIdentifier identifier; | |
| 41 | |
| 42 _KeywordVisitor(this.request); | |
| 43 | 41 |
| 44 @override | 42 @override |
| 45 visitBlock(Block node) { | 43 visitBlock(Block node) { |
| 46 if (_isInClassMemberBody(node)) { | 44 if (_isInClassMemberBody(node)) { |
| 47 _addSuggestions([ | 45 _addSuggestions([Keyword.SUPER, Keyword.THIS,]); |
| 48 Keyword.ASSERT, | |
| 49 Keyword.CASE, | |
| 50 Keyword.CONTINUE, | |
| 51 Keyword.DO, | |
| 52 Keyword.FINAL, | |
| 53 Keyword.FOR, | |
| 54 Keyword.IF, | |
| 55 Keyword.NEW, | |
| 56 Keyword.RETHROW, | |
| 57 Keyword.RETURN, | |
| 58 Keyword.SUPER, | |
| 59 Keyword.SWITCH, | |
| 60 Keyword.THIS, | |
| 61 Keyword.THROW, | |
| 62 Keyword.TRY, | |
| 63 Keyword.VAR, | |
| 64 Keyword.VOID, | |
| 65 Keyword.WHILE | |
| 66 ]); | |
| 67 } else { | |
| 68 _addSuggestions([ | |
| 69 Keyword.ASSERT, | |
| 70 Keyword.CASE, | |
| 71 Keyword.CONTINUE, | |
| 72 Keyword.DO, | |
| 73 Keyword.FINAL, | |
| 74 Keyword.FOR, | |
| 75 Keyword.IF, | |
| 76 Keyword.NEW, | |
| 77 Keyword.RETHROW, | |
| 78 Keyword.RETURN, | |
| 79 Keyword.SWITCH, | |
| 80 Keyword.THROW, | |
| 81 Keyword.TRY, | |
| 82 Keyword.VAR, | |
| 83 Keyword.VOID, | |
| 84 Keyword.WHILE | |
| 85 ]); | |
| 86 } | 46 } |
| 47 _addSuggestions([ |
| 48 Keyword.ASSERT, |
| 49 Keyword.CASE, |
| 50 Keyword.CONTINUE, |
| 51 Keyword.DO, |
| 52 Keyword.FINAL, |
| 53 Keyword.FOR, |
| 54 Keyword.IF, |
| 55 Keyword.NEW, |
| 56 Keyword.RETHROW, |
| 57 Keyword.RETURN, |
| 58 Keyword.SWITCH, |
| 59 Keyword.THROW, |
| 60 Keyword.TRY, |
| 61 Keyword.VAR, |
| 62 Keyword.VOID, |
| 63 Keyword.WHILE |
| 64 ]); |
| 87 } | 65 } |
| 88 | 66 |
| 89 @override | 67 @override |
| 90 visitClassDeclaration(ClassDeclaration node) { | 68 visitClassDeclaration(ClassDeclaration node) { |
| 91 // Don't suggest class name | 69 // Don't suggest class name |
| 92 if (node.name == identifier) { | 70 if (entity == node.name) { |
| 93 return; | 71 return; |
| 94 } | 72 } |
| 95 // Inside the class declaration { } | 73 if (entity == node.rightBracket) { |
| 96 if (request.offset > node.leftBracket.offset) { | |
| 97 _addSuggestions([ | 74 _addSuggestions([ |
| 98 Keyword.CONST, | 75 Keyword.CONST, |
| 99 Keyword.DYNAMIC, | 76 Keyword.DYNAMIC, |
| 100 Keyword.FACTORY, | 77 Keyword.FACTORY, |
| 101 Keyword.FINAL, | 78 Keyword.FINAL, |
| 102 Keyword.GET, | 79 Keyword.GET, |
| 103 Keyword.OPERATOR, | 80 Keyword.OPERATOR, |
| 104 Keyword.SET, | 81 Keyword.SET, |
| 105 Keyword.STATIC, | 82 Keyword.STATIC, |
| 106 Keyword.VAR, | 83 Keyword.VAR, |
| 107 Keyword.VOID | 84 Keyword.VOID |
| 108 ]); | 85 ]); |
| 109 return; | 86 return; |
| 110 } | 87 } |
| 111 _addClassDeclarationKeywords(node); | 88 _addClassDeclarationKeywords(node); |
| 112 } | 89 } |
| 113 | 90 |
| 114 @override | 91 @override |
| 115 visitCompilationUnit(CompilationUnit node) { | 92 visitCompilationUnit(CompilationUnit node) { |
| 116 Directive firstDirective; | 93 var previousMember = null; |
| 117 int endOfDirectives = 0; | 94 for (var member in node.childEntities) { |
| 118 if (node.directives.length > 0) { | 95 if (entity == member) { |
| 119 firstDirective = node.directives[0]; | 96 break; |
| 120 endOfDirectives = node.directives.last.end - 1; | 97 } |
| 98 previousMember = member; |
| 121 } | 99 } |
| 122 int startOfDeclarations = node.end; | 100 if (previousMember is ClassDeclaration) { |
| 123 if (node.declarations.length > 0) { | 101 if (previousMember.leftBracket == null || |
| 124 startOfDeclarations = node.declarations[0].offset; | 102 previousMember.leftBracket.isSynthetic) { |
| 125 // If the first token is a simple identifier | 103 // If the prior member is an unfinished class declaration |
| 126 // and cursor position in within that first token | 104 // then the user is probably finishing that |
| 127 // then consider cursor to be before the first declaration | 105 _addClassDeclarationKeywords(previousMember); |
| 128 Token token = node.declarations[0].firstTokenAfterCommentAndMetadata; | 106 return; |
| 129 if (token.offset <= request.offset && request.offset <= token.end) { | |
| 130 startOfDeclarations = token.end; | |
| 131 } | 107 } |
| 132 } | 108 } |
| 133 | 109 if (previousMember == null || previousMember is Directive) { |
| 134 // Simplistic check for library as first directive | 110 if (previousMember == null && |
| 135 if (firstDirective is! LibraryDirective) { | 111 !node.directives.any((d) => d is LibraryDirective)) { |
| 136 if (firstDirective != null) { | 112 _addSuggestions([Keyword.LIBRARY], DART_RELEVANCE_HIGH); |
| 137 if (request.offset <= firstDirective.offset) { | |
| 138 _addSuggestions([Keyword.LIBRARY], DART_RELEVANCE_HIGH); | |
| 139 } | |
| 140 } else { | |
| 141 if (request.offset <= startOfDeclarations) { | |
| 142 _addSuggestions([Keyword.LIBRARY], DART_RELEVANCE_HIGH); | |
| 143 } | |
| 144 } | 113 } |
| 145 } | |
| 146 if (request.offset <= startOfDeclarations) { | |
| 147 _addSuggestions( | 114 _addSuggestions( |
| 148 [Keyword.EXPORT, Keyword.IMPORT, Keyword.PART], DART_RELEVANCE_HIGH); | 115 [Keyword.EXPORT, Keyword.IMPORT, Keyword.PART], DART_RELEVANCE_HIGH); |
| 149 } | 116 } |
| 150 if (request.offset >= endOfDirectives) { | 117 if (entity == null || entity is Declaration) { |
| 151 _addSuggestions([ | 118 _addSuggestions([ |
| 152 Keyword.ABSTRACT, | 119 Keyword.ABSTRACT, |
| 153 Keyword.CLASS, | 120 Keyword.CLASS, |
| 154 Keyword.CONST, | 121 Keyword.CONST, |
| 155 Keyword.FINAL, | 122 Keyword.FINAL, |
| 156 Keyword.TYPEDEF, | 123 Keyword.TYPEDEF, |
| 157 Keyword.VAR | 124 Keyword.VAR |
| 158 ], DART_RELEVANCE_HIGH); | 125 ], DART_RELEVANCE_HIGH); |
| 159 } | 126 } |
| 160 } | 127 } |
| 161 | 128 |
| 162 @override | |
| 163 visitNode(AstNode node) { | |
| 164 if (_isOffsetAfterNode(node)) { | |
| 165 node.parent.accept(this); | |
| 166 } | |
| 167 } | |
| 168 | |
| 169 visitSimpleIdentifier(SimpleIdentifier node) { | |
| 170 identifier = node; | |
| 171 node.parent.accept(this); | |
| 172 } | |
| 173 | |
| 174 void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { | |
| 175 if (identifier != null && node.beginToken == identifier.beginToken) { | |
| 176 AstNode unit = node.parent; | |
| 177 if (unit is CompilationUnit) { | |
| 178 CompilationUnitMember previous; | |
| 179 for (CompilationUnitMember member in unit.declarations) { | |
| 180 if (member == node && previous is ClassDeclaration) { | |
| 181 if (previous.endToken.isSynthetic) { | |
| 182 // Partial keywords (simple identifirs) that are part of a | |
| 183 // class declaration can be parsed | |
| 184 // as a TypeName in a TopLevelVariableDeclaration | |
| 185 _addClassDeclarationKeywords(previous); | |
| 186 return; | |
| 187 } | |
| 188 } | |
| 189 previous = member; | |
| 190 } | |
| 191 // Partial keywords (simple identifiers) can be parsed | |
| 192 // as a TypeName in a TopLevelVariableDeclaration | |
| 193 unit.accept(this); | |
| 194 } | |
| 195 } | |
| 196 } | |
| 197 | |
| 198 void visitTypeName(TypeName node) { | |
| 199 node.parent.accept(this); | |
| 200 } | |
| 201 | |
| 202 void visitVariableDeclarationList(VariableDeclarationList node) { | |
| 203 node.parent.accept(this); | |
| 204 } | |
| 205 | |
| 206 void _addClassDeclarationKeywords(ClassDeclaration node) { | 129 void _addClassDeclarationKeywords(ClassDeclaration node) { |
| 207 // Very simplistic suggestion because analyzer will warn if | 130 // Very simplistic suggestion because analyzer will warn if |
| 208 // the extends / with / implements keywords are out of order | 131 // the extends / with / implements keywords are out of order |
| 209 if (node.extendsClause == null) { | 132 if (node.extendsClause == null) { |
| 210 _addSuggestion(Keyword.EXTENDS, DART_RELEVANCE_HIGH); | 133 _addSuggestion(Keyword.EXTENDS, DART_RELEVANCE_HIGH); |
| 211 } else if (node.withClause == null) { | 134 } else if (node.withClause == null) { |
| 212 _addSuggestion(Keyword.WITH, DART_RELEVANCE_HIGH); | 135 _addSuggestion(Keyword.WITH, DART_RELEVANCE_HIGH); |
| 213 } | 136 } |
| 214 if (node.implementsClause == null) { | 137 if (node.implementsClause == null) { |
| 215 _addSuggestion(Keyword.IMPLEMENTS, DART_RELEVANCE_HIGH); | 138 _addSuggestion(Keyword.IMPLEMENTS, DART_RELEVANCE_HIGH); |
| 216 } | 139 } |
| 217 } | 140 } |
| 218 | 141 |
| 219 void _addSuggestion(Keyword keyword, | 142 void _addSuggestion(Keyword keyword, |
| 220 [int relevance = DART_RELEVANCE_DEFAULT]) { | 143 [int relevance = DART_RELEVANCE_DEFAULT]) { |
| 221 String completion = keyword.syntax; | 144 String completion = keyword.syntax; |
| 222 request.addSuggestion(new CompletionSuggestion( | 145 request.addSuggestion(new CompletionSuggestion( |
| 223 CompletionSuggestionKind.KEYWORD, | 146 CompletionSuggestionKind.KEYWORD, relevance, completion, |
| 224 relevance, completion, completion.length, 0, false, false)); | 147 completion.length, 0, false, false)); |
| 225 } | 148 } |
| 226 | 149 |
| 227 void _addSuggestions(List<Keyword> keywords, | 150 void _addSuggestions(List<Keyword> keywords, |
| 228 [int relevance = DART_RELEVANCE_KEYWORD]) { | 151 [int relevance = DART_RELEVANCE_KEYWORD]) { |
| 229 keywords.forEach((Keyword keyword) { | 152 keywords.forEach((Keyword keyword) { |
| 230 _addSuggestion(keyword, relevance); | 153 _addSuggestion(keyword, relevance); |
| 231 }); | 154 }); |
| 232 } | 155 } |
| 233 | 156 |
| 234 bool _isOffsetAfterNode(AstNode node) { | 157 bool _isInClassMemberBody(AstNode node) { |
| 235 if (request.offset == node.end) { | |
| 236 Token token = node.endToken; | |
| 237 if (token != null && !token.isSynthetic) { | |
| 238 if (token.lexeme == ';' || token.lexeme == '}') { | |
| 239 return true; | |
| 240 } | |
| 241 } | |
| 242 } | |
| 243 return false; | |
| 244 } | |
| 245 | |
| 246 static bool _isInClassMemberBody(AstNode node) { | |
| 247 while (true) { | 158 while (true) { |
| 248 AstNode body = node.getAncestor((n) => n is FunctionBody); | 159 AstNode body = node.getAncestor((n) => n is FunctionBody); |
| 249 if (body == null) { | 160 if (body == null) { |
| 250 return false; | 161 return false; |
| 251 } | 162 } |
| 252 AstNode parent = body.parent; | 163 AstNode parent = body.parent; |
| 253 if (parent is ConstructorDeclaration || parent is MethodDeclaration) { | 164 if (parent is ConstructorDeclaration || parent is MethodDeclaration) { |
| 254 return true; | 165 return true; |
| 255 } | 166 } |
| 256 node = parent; | 167 node = parent; |
| 257 } | 168 } |
| 258 } | 169 } |
| 259 } | 170 } |
| OLD | NEW |