| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, 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 analysis_server.src.provisional.completion.dart.completion_target; | 5 library analysis_server.src.provisional.completion.dart.completion_target; |
| 6 | 6 |
| 7 import 'package:analyzer/src/generated/ast.dart'; | 7 import 'package:analyzer/src/generated/ast.dart'; |
| 8 import 'package:analyzer/src/generated/element.dart'; | 8 import 'package:analyzer/src/generated/element.dart'; |
| 9 import 'package:analyzer/src/generated/scanner.dart'; | 9 import 'package:analyzer/src/generated/scanner.dart'; |
| 10 import 'package:analyzer/src/generated/utilities_dart.dart'; | 10 import 'package:analyzer/src/generated/utilities_dart.dart'; |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 84 * | 84 * |
| 85 * Clients may not extend, implement or mix-in this class. | 85 * Clients may not extend, implement or mix-in this class. |
| 86 */ | 86 */ |
| 87 class CompletionTarget { | 87 class CompletionTarget { |
| 88 /** | 88 /** |
| 89 * The compilation unit in which the completion is occurring. | 89 * The compilation unit in which the completion is occurring. |
| 90 */ | 90 */ |
| 91 final CompilationUnit unit; | 91 final CompilationUnit unit; |
| 92 | 92 |
| 93 /** | 93 /** |
| 94 * The offset within the source at which the completion is being requested. |
| 95 */ |
| 96 final int offset; |
| 97 |
| 98 /** |
| 94 * The context in which the completion is occurring. This is the AST node | 99 * The context in which the completion is occurring. This is the AST node |
| 95 * which is a direct parent of [entity]. | 100 * which is a direct parent of [entity]. |
| 96 */ | 101 */ |
| 97 final AstNode containingNode; | 102 final AstNode containingNode; |
| 98 | 103 |
| 99 /** | 104 /** |
| 100 * The entity which the completed text will replace (or which will be | 105 * The entity which the completed text will replace (or which will be |
| 101 * displaced once the completed text is inserted). This may be an AstNode or | 106 * displaced once the completed text is inserted). This may be an AstNode or |
| 102 * a Token, or it may be null if the cursor is after all tokens in the file. | 107 * a Token, or it may be null if the cursor is after all tokens in the file. |
| 103 * | 108 * |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 155 } | 160 } |
| 156 } | 161 } |
| 157 } | 162 } |
| 158 for (var entity in containingNode.childEntities) { | 163 for (var entity in containingNode.childEntities) { |
| 159 if (entity is Token) { | 164 if (entity is Token) { |
| 160 if (_isCandidateToken(entity, offset)) { | 165 if (_isCandidateToken(entity, offset)) { |
| 161 // Try to replace with a comment token. | 166 // Try to replace with a comment token. |
| 162 Token commentToken = _getContainingCommentToken(entity, offset); | 167 Token commentToken = _getContainingCommentToken(entity, offset); |
| 163 if (commentToken != null) { | 168 if (commentToken != null) { |
| 164 return new CompletionTarget._( | 169 return new CompletionTarget._( |
| 165 compilationUnit, containingNode, commentToken, true); | 170 compilationUnit, offset, containingNode, commentToken, true); |
| 166 } | 171 } |
| 167 // Target found. | 172 // Target found. |
| 168 return new CompletionTarget._( | 173 return new CompletionTarget._( |
| 169 compilationUnit, containingNode, entity, false); | 174 compilationUnit, offset, containingNode, entity, false); |
| 170 } else { | 175 } else { |
| 171 // Since entity is a token, we don't need to look inside it; just | 176 // Since entity is a token, we don't need to look inside it; just |
| 172 // proceed to the next entity. | 177 // proceed to the next entity. |
| 173 continue; | 178 continue; |
| 174 } | 179 } |
| 175 } else if (entity is AstNode) { | 180 } else if (entity is AstNode) { |
| 176 // If the last token in the node isn't a candidate target, then | 181 // If the last token in the node isn't a candidate target, then |
| 177 // neither the node nor any of its descendants can possibly be the | 182 // neither the node nor any of its descendants can possibly be the |
| 178 // completion target, so we can skip the node entirely. | 183 // completion target, so we can skip the node entirely. |
| 179 if (!_isCandidateToken(entity.endToken, offset)) { | 184 if (!_isCandidateToken(entity.endToken, offset)) { |
| 180 continue; | 185 continue; |
| 181 } | 186 } |
| 182 | 187 |
| 183 // If the node is a candidate target, then we are done. | 188 // If the node is a candidate target, then we are done. |
| 184 if (_isCandidateNode(entity, offset)) { | 189 if (_isCandidateNode(entity, offset)) { |
| 185 // Check to see if the offset is in a preceding comment | 190 // Check to see if the offset is in a preceding comment |
| 186 Token commentToken = | 191 Token commentToken = |
| 187 _getContainingCommentToken(entity.beginToken, offset); | 192 _getContainingCommentToken(entity.beginToken, offset); |
| 188 if (commentToken != null) { | 193 if (commentToken != null) { |
| 189 entity = commentToken; | 194 entity = commentToken; |
| 190 // If the preceding comment is dartdoc token, then update | 195 // If the preceding comment is dartdoc token, then update |
| 191 // the containing node to be the dartdoc comment. | 196 // the containing node to be the dartdoc comment. |
| 192 // Otherwise completion is not required. | 197 // Otherwise completion is not required. |
| 193 Comment docComment = | 198 Comment docComment = |
| 194 _getContainingDocComment(containingNode, commentToken); | 199 _getContainingDocComment(containingNode, commentToken); |
| 195 if (docComment != null) { | 200 if (docComment != null) { |
| 196 containingNode = docComment; | 201 containingNode = docComment; |
| 197 } else { | 202 } else { |
| 198 return new CompletionTarget._( | 203 return new CompletionTarget._(compilationUnit, offset, |
| 199 compilationUnit, compilationUnit, commentToken, true); | 204 compilationUnit, commentToken, true); |
| 200 } | 205 } |
| 201 } | 206 } |
| 202 return new CompletionTarget._( | 207 return new CompletionTarget._( |
| 203 compilationUnit, containingNode, entity, false); | 208 compilationUnit, offset, containingNode, entity, false); |
| 204 } | 209 } |
| 205 | 210 |
| 206 // Otherwise, the completion target is somewhere inside the entity, | 211 // Otherwise, the completion target is somewhere inside the entity, |
| 207 // so we need to jump to the start of the outer loop to examine its | 212 // so we need to jump to the start of the outer loop to examine its |
| 208 // contents. | 213 // contents. |
| 209 containingNode = entity; | 214 containingNode = entity; |
| 210 continue outerLoop; | 215 continue outerLoop; |
| 211 } else { | 216 } else { |
| 212 // Unexpected entity found (all entities in a parse tree should be | 217 // Unexpected entity found (all entities in a parse tree should be |
| 213 // AST nodes or tokens). | 218 // AST nodes or tokens). |
| 214 assert(false); | 219 assert(false); |
| 215 } | 220 } |
| 216 } | 221 } |
| 217 | 222 |
| 218 // No completion target found. It should only be possible to reach here | 223 // No completion target found. It should only be possible to reach here |
| 219 // the first time through the outer loop (since we only jump to the start | 224 // the first time through the outer loop (since we only jump to the start |
| 220 // of the outer loop after determining that the completion target is | 225 // of the outer loop after determining that the completion target is |
| 221 // inside an entity). We can check that assumption by verifying that | 226 // inside an entity). We can check that assumption by verifying that |
| 222 // containingNode is still the compilationUnit. | 227 // containingNode is still the compilationUnit. |
| 223 assert(identical(containingNode, compilationUnit)); | 228 assert(identical(containingNode, compilationUnit)); |
| 224 | 229 |
| 225 // Since no completion target was found, we set the completion target | 230 // Since no completion target was found, we set the completion target |
| 226 // entity to null and use the compilationUnit as the parent. | 231 // entity to null and use the compilationUnit as the parent. |
| 227 return new CompletionTarget._( | 232 return new CompletionTarget._( |
| 228 compilationUnit, compilationUnit, null, false); | 233 compilationUnit, offset, compilationUnit, null, false); |
| 229 } | 234 } |
| 230 } | 235 } |
| 231 | 236 |
| 232 /** | 237 /** |
| 233 * Create a [CompletionTarget] holding the given [containingNode] and | 238 * Create a [CompletionTarget] holding the given [containingNode] and |
| 234 * [entity]. | 239 * [entity]. |
| 235 */ | 240 */ |
| 236 CompletionTarget._( | 241 CompletionTarget._(this.unit, this.offset, AstNode containingNode, |
| 237 this.unit, AstNode containingNode, Object entity, this.isCommentText) | 242 Object entity, this.isCommentText) |
| 238 : this.containingNode = containingNode, | 243 : this.containingNode = containingNode, |
| 239 this.entity = entity, | 244 this.entity = entity, |
| 240 this.argIndex = _computeArgIndex(containingNode, entity); | 245 this.argIndex = _computeArgIndex(containingNode, entity); |
| 241 | 246 |
| 242 /** | 247 /** |
| 248 * Return `true` if the [containingNode] is a cascade |
| 249 * and the completion insertion is not between the two dots. |
| 250 * For example, `..d^` and `..^d` are considered a cascade |
| 251 * from a completion standpoint, but `.^.d` is not. |
| 252 */ |
| 253 bool get isCascade { |
| 254 AstNode node = containingNode; |
| 255 if (node is PropertyAccess) { |
| 256 return node.isCascaded && offset > node.operator.offset + 1; |
| 257 } |
| 258 if (node is MethodInvocation) { |
| 259 return node.isCascaded && offset > node.operator.offset + 1; |
| 260 } |
| 261 return false; |
| 262 } |
| 263 |
| 264 /** |
| 243 * Return `true` if the target is a functional argument in an argument list. | 265 * Return `true` if the target is a functional argument in an argument list. |
| 244 * The target [AstNode] hierarchy *must* be resolved for this to work. | 266 * The target [AstNode] hierarchy *must* be resolved for this to work. |
| 245 */ | 267 */ |
| 246 bool isFunctionalArgument() { | 268 bool isFunctionalArgument() { |
| 247 if (argIndex == null) { | 269 if (argIndex == null) { |
| 248 return false; | 270 return false; |
| 249 } | 271 } |
| 250 AstNode argList = containingNode; | 272 AstNode argList = containingNode; |
| 251 if (argList is! ArgumentList) { | 273 if (argList is! ArgumentList) { |
| 252 return false; | 274 return false; |
| (...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 380 if (param.parameterKind == ParameterKind.NAMED) { | 402 if (param.parameterKind == ParameterKind.NAMED) { |
| 381 // TODO(danrubel) handle named parameters | 403 // TODO(danrubel) handle named parameters |
| 382 return false; | 404 return false; |
| 383 } else { | 405 } else { |
| 384 return paramType is FunctionType || paramType is FunctionTypeAlias; | 406 return paramType is FunctionType || paramType is FunctionTypeAlias; |
| 385 } | 407 } |
| 386 } | 408 } |
| 387 return false; | 409 return false; |
| 388 } | 410 } |
| 389 } | 411 } |
| OLD | NEW |