| 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.toplevel; | 5 library services.completion.computer.dart.toplevel; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 | 8 |
| 9 import 'package:analysis_server/src/protocol_server.dart' hide Element, | 9 import 'package:analysis_server/src/protocol_server.dart' hide Element, |
| 10 ElementKind; | 10 ElementKind; |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 51 Future<bool> visitBlock(Block node) { | 51 Future<bool> visitBlock(Block node) { |
| 52 return _addImportedElementSuggestions(node); | 52 return _addImportedElementSuggestions(node); |
| 53 } | 53 } |
| 54 | 54 |
| 55 @override | 55 @override |
| 56 Future<bool> visitCascadeExpression(CascadeExpression node) { | 56 Future<bool> visitCascadeExpression(CascadeExpression node) { |
| 57 // Make suggestions for the target, but not for the selector | 57 // Make suggestions for the target, but not for the selector |
| 58 // InvocationComputer makes selector suggestions | 58 // InvocationComputer makes selector suggestions |
| 59 Expression target = node.target; | 59 Expression target = node.target; |
| 60 if (target != null && request.offset <= target.end) { | 60 if (target != null && request.offset <= target.end) { |
| 61 return _addImportedElementSuggestions(node); | 61 return _addImportedElementSuggestions(node, excludeVoidReturn: true); |
| 62 } | 62 } |
| 63 return new Future.value(false); | 63 return new Future.value(false); |
| 64 } | 64 } |
| 65 | 65 |
| 66 @override | 66 @override |
| 67 Future<bool> visitClassDeclaration(ClassDeclaration node) { | 67 Future<bool> visitClassDeclaration(ClassDeclaration node) { |
| 68 // Make suggestions in the body of the class declaration | 68 // Make suggestions in the body of the class declaration |
| 69 Token leftBracket = node.leftBracket; | 69 Token leftBracket = node.leftBracket; |
| 70 if (leftBracket != null && request.offset >= leftBracket.end) { | 70 if (leftBracket != null && request.offset >= leftBracket.end) { |
| 71 return _addImportedElementSuggestions(node); | 71 return _addImportedElementSuggestions(node); |
| 72 } | 72 } |
| 73 return new Future.value(false); | 73 return new Future.value(false); |
| 74 } | 74 } |
| 75 | 75 |
| 76 @override | 76 @override |
| 77 Future<bool> visitCombinator(Combinator node) { | 77 Future<bool> visitCombinator(Combinator node) { |
| 78 return _addCombinatorSuggestions(node); | 78 return _addCombinatorSuggestions(node); |
| 79 } | 79 } |
| 80 | 80 |
| 81 @override | 81 @override |
| 82 Future<bool> visitExpression(Expression node) { | 82 Future<bool> visitExpression(Expression node) { |
| 83 return _addImportedElementSuggestions(node); | 83 return _addImportedElementSuggestions(node, excludeVoidReturn: true); |
| 84 } | 84 } |
| 85 | 85 |
| 86 @override | 86 @override |
| 87 Future<bool> visitExpressionStatement(ExpressionStatement node) { | 87 Future<bool> visitExpressionStatement(ExpressionStatement node) { |
| 88 Expression expression = node.expression; | 88 Expression expression = node.expression; |
| 89 // A pre-variable declaration (e.g. C ^) is parsed as an expression | 89 // A pre-variable declaration (e.g. C ^) is parsed as an expression |
| 90 // statement. Do not make suggestions for the variable name. | 90 // statement. Do not make suggestions for the variable name. |
| 91 if (expression is SimpleIdentifier && request.offset <= expression.end) { | 91 if (expression is SimpleIdentifier && request.offset <= expression.end) { |
| 92 return _addImportedElementSuggestions(node); | 92 return _addImportedElementSuggestions(node); |
| 93 } | 93 } |
| 94 return new Future.value(false); | 94 return new Future.value(false); |
| 95 } | 95 } |
| 96 | 96 |
| 97 @override | 97 @override |
| 98 Future<bool> visitForStatement(ForStatement node) { | 98 Future<bool> visitForStatement(ForStatement node) { |
| 99 Token leftParenthesis = node.leftParenthesis; | 99 Token leftParenthesis = node.leftParenthesis; |
| 100 if (leftParenthesis != null && request.offset >= leftParenthesis.end) { | 100 if (leftParenthesis != null && request.offset >= leftParenthesis.end) { |
| 101 return _addImportedElementSuggestions(node); | 101 return _addImportedElementSuggestions(node); |
| 102 } | 102 } |
| 103 return new Future.value(false); | 103 return new Future.value(false); |
| 104 } | 104 } |
| 105 | 105 |
| 106 @override | 106 @override |
| 107 Future<bool> visitIfStatement(IfStatement node) { | 107 Future<bool> visitIfStatement(IfStatement node) { |
| 108 Token leftParen = node.leftParenthesis; | 108 Token leftParen = node.leftParenthesis; |
| 109 if (leftParen != null && request.offset >= leftParen.end) { | 109 if (leftParen != null && request.offset >= leftParen.end) { |
| 110 Token rightParen = node.rightParenthesis; | 110 Token rightParen = node.rightParenthesis; |
| 111 if (rightParen == null || request.offset <= rightParen.offset) { | 111 if (rightParen == null || request.offset <= rightParen.offset) { |
| 112 return _addImportedElementSuggestions(node); | 112 return _addImportedElementSuggestions(node, excludeVoidReturn: true); |
| 113 } | 113 } |
| 114 } | 114 } |
| 115 return new Future.value(false); | 115 return new Future.value(false); |
| 116 } | 116 } |
| 117 | 117 |
| 118 @override | 118 @override |
| 119 Future<bool> visitInterpolationExpression(InterpolationExpression node) { | 119 Future<bool> visitInterpolationExpression(InterpolationExpression node) { |
| 120 Expression expression = node.expression; | 120 Expression expression = node.expression; |
| 121 if (expression is SimpleIdentifier) { | 121 if (expression is SimpleIdentifier) { |
| 122 return _addImportedElementSuggestions(node); | 122 return _addImportedElementSuggestions(node, excludeVoidReturn: true); |
| 123 } | 123 } |
| 124 return new Future.value(false); | 124 return new Future.value(false); |
| 125 } | 125 } |
| 126 | 126 |
| 127 @override | 127 @override |
| 128 Future<bool> visitMethodInvocation(MethodInvocation node) { | 128 Future<bool> visitMethodInvocation(MethodInvocation node) { |
| 129 Token period = node.period; | 129 Token period = node.period; |
| 130 if (period == null || request.offset <= period.offset) { | 130 if (period == null || request.offset <= period.offset) { |
| 131 return _addImportedElementSuggestions(node); | 131 return _addImportedElementSuggestions(node, excludeVoidReturn: true); |
| 132 } | 132 } |
| 133 return new Future.value(false); | 133 return new Future.value(false); |
| 134 } | 134 } |
| 135 | 135 |
| 136 @override | 136 @override |
| 137 Future<bool> visitNode(AstNode node) { | 137 Future<bool> visitNode(AstNode node) { |
| 138 return new Future.value(false); | 138 return new Future.value(false); |
| 139 } | 139 } |
| 140 | 140 |
| 141 @override | 141 @override |
| 142 Future<bool> visitPrefixedIdentifier(PrefixedIdentifier node) { | 142 Future<bool> visitPrefixedIdentifier(PrefixedIdentifier node) { |
| 143 // Make suggestions for the prefix, but not for the selector | 143 // Make suggestions for the prefix, but not for the selector |
| 144 // InvocationComputer makes selector suggestions | 144 // InvocationComputer makes selector suggestions |
| 145 Token period = node.period; | 145 Token period = node.period; |
| 146 if (period != null && request.offset <= period.offset) { | 146 if (period != null && request.offset <= period.offset) { |
| 147 return _addImportedElementSuggestions(node); | 147 return _addImportedElementSuggestions(node, excludeVoidReturn: true); |
| 148 } | 148 } |
| 149 return new Future.value(false); | 149 return new Future.value(false); |
| 150 } | 150 } |
| 151 | 151 |
| 152 @override | 152 @override |
| 153 Future<bool> visitPropertyAccess(PropertyAccess node) { | 153 Future<bool> visitPropertyAccess(PropertyAccess node) { |
| 154 // Make suggestions for the target, but not for the property name | 154 // Make suggestions for the target, but not for the property name |
| 155 // InvocationComputer makes property name suggestions | 155 // InvocationComputer makes property name suggestions |
| 156 var operator = node.operator; | 156 var operator = node.operator; |
| 157 if (operator != null && request.offset < operator.offset) { | 157 if (operator != null && request.offset < operator.offset) { |
| 158 return _addImportedElementSuggestions(node); | 158 return _addImportedElementSuggestions(node, excludeVoidReturn: true); |
| 159 } | 159 } |
| 160 return new Future.value(false); | 160 return new Future.value(false); |
| 161 } | 161 } |
| 162 | 162 |
| 163 @override | 163 @override |
| 164 Future<bool> visitSimpleIdentifier(SimpleIdentifier node) { | 164 Future<bool> visitSimpleIdentifier(SimpleIdentifier node) { |
| 165 return node.parent.accept(this); | 165 return node.parent.accept(this); |
| 166 } | 166 } |
| 167 | 167 |
| 168 @override | 168 @override |
| 169 Future<bool> visitStringLiteral(StringLiteral node) { | 169 Future<bool> visitStringLiteral(StringLiteral node) { |
| 170 return new Future.value(false); | 170 return new Future.value(false); |
| 171 } | 171 } |
| 172 | 172 |
| 173 @override | 173 @override |
| 174 Future<bool> visitTypeName(TypeName node) { | 174 Future<bool> visitTypeName(TypeName node) { |
| 175 return _addImportedElementSuggestions(node, typesOnly: true); | 175 return _addImportedElementSuggestions(node, typesOnly: true); |
| 176 } | 176 } |
| 177 | 177 |
| 178 @override | 178 @override |
| 179 visitVariableDeclaration(VariableDeclaration node) { | 179 visitVariableDeclaration(VariableDeclaration node) { |
| 180 Token equals = node.equals; | 180 Token equals = node.equals; |
| 181 // Make suggestions for the RHS of a variable declaration | 181 // Make suggestions for the RHS of a variable declaration |
| 182 if (equals != null && request.offset >= equals.end) { | 182 if (equals != null && request.offset >= equals.end) { |
| 183 return _addImportedElementSuggestions(node); | 183 return _addImportedElementSuggestions(node, excludeVoidReturn: true); |
| 184 } | 184 } |
| 185 return new Future.value(false); | 185 return new Future.value(false); |
| 186 } | 186 } |
| 187 | 187 |
| 188 Future _addCombinatorSuggestions(Combinator node) { | 188 Future _addCombinatorSuggestions(Combinator node) { |
| 189 var directive = node.getAncestor((parent) => parent is NamespaceDirective); | 189 var directive = node.getAncestor((parent) => parent is NamespaceDirective); |
| 190 if (directive is NamespaceDirective) { | 190 if (directive is NamespaceDirective) { |
| 191 LibraryElement library = directive.uriElement; | 191 LibraryElement library = directive.uriElement; |
| 192 LibraryElementSuggestionBuilder.suggestionsFor(request, library); | 192 LibraryElementSuggestionBuilder.suggestionsFor(request, library); |
| 193 return new Future.value(true); | 193 return new Future.value(true); |
| 194 } | 194 } |
| 195 | 195 |
| 196 return new Future.value(false); | 196 return new Future.value(false); |
| 197 } | 197 } |
| 198 | 198 |
| 199 void _addElementSuggestion(Element element, CompletionRelevance relevance) { | 199 void _addElementSuggestion(Element element, bool typesOnly, bool excludeVoidRe
turn, CompletionRelevance relevance) { |
| 200 | 200 |
| 201 if (element is ExecutableElement) { | 201 if (element is ExecutableElement) { |
| 202 if (element.isOperator) { | 202 if (element.isOperator) { |
| 203 return; | 203 return; |
| 204 } | 204 } |
| 205 if (excludeVoidReturn) { |
| 206 DartType returnType = element.returnType; |
| 207 if (returnType != null && returnType.isVoid) { |
| 208 return; |
| 209 } |
| 210 } |
| 211 } |
| 212 if (typesOnly && element is! ClassElement) { |
| 213 return; |
| 205 } | 214 } |
| 206 | 215 |
| 207 CompletionSuggestionKind kind = | 216 CompletionSuggestionKind kind = |
| 208 newCompletionSuggestionKind_fromElementKind(element.kind); | 217 newCompletionSuggestionKind_fromElementKind(element.kind); |
| 209 | 218 |
| 210 String completion = element.displayName; | 219 String completion = element.displayName; |
| 211 CompletionSuggestion suggestion = new CompletionSuggestion( | 220 CompletionSuggestion suggestion = new CompletionSuggestion( |
| 212 kind, | 221 kind, |
| 213 relevance, | 222 relevance, |
| 214 completion, | 223 completion, |
| (...skipping 15 matching lines...) Expand all Loading... |
| 230 if (type != null) { | 239 if (type != null) { |
| 231 String name = type.displayName; | 240 String name = type.displayName; |
| 232 if (name != null && name.length > 0 && name != 'dynamic') { | 241 if (name != null && name.length > 0 && name != 'dynamic') { |
| 233 suggestion.returnType = name; | 242 suggestion.returnType = name; |
| 234 } | 243 } |
| 235 } | 244 } |
| 236 | 245 |
| 237 request.suggestions.add(suggestion); | 246 request.suggestions.add(suggestion); |
| 238 } | 247 } |
| 239 | 248 |
| 240 void _addElementSuggestions(List<Element> elements) { | 249 void _addElementSuggestions(List<Element> elements, bool typesOnly, bool exclu
deVoidReturn) { |
| 241 elements.forEach((Element elem) { | 250 elements.forEach((Element elem) { |
| 242 _addElementSuggestion(elem, CompletionRelevance.DEFAULT); | 251 _addElementSuggestion(elem, typesOnly, excludeVoidReturn, CompletionReleva
nce.DEFAULT); |
| 243 }); | 252 }); |
| 244 } | 253 } |
| 245 | 254 |
| 246 Future<bool> _addImportedElementSuggestions(AstNode node, {bool typesOnly: | 255 Future<bool> _addImportedElementSuggestions(AstNode node, {bool typesOnly: |
| 247 false}) { | 256 false, bool excludeVoidReturn: false}) { |
| 248 | 257 |
| 249 // Exclude elements from local library | 258 // Exclude elements from local library |
| 250 // because they are provided by LocalComputer | 259 // because they are provided by LocalComputer |
| 251 Set<LibraryElement> excludedLibs = new Set<LibraryElement>(); | 260 Set<LibraryElement> excludedLibs = new Set<LibraryElement>(); |
| 252 excludedLibs.add(request.unit.element.enclosingElement); | 261 excludedLibs.add(request.unit.element.enclosingElement); |
| 253 | 262 |
| 254 // Include explicitly imported elements | 263 // Include explicitly imported elements |
| 255 Map<String, ClassElement> classMap = new Map<String, ClassElement>(); | 264 Map<String, ClassElement> classMap = new Map<String, ClassElement>(); |
| 256 request.unit.directives.forEach((Directive directive) { | 265 request.unit.directives.forEach((Directive directive) { |
| 257 if (directive is ImportDirective) { | 266 if (directive is ImportDirective) { |
| 258 ImportElement importElem = directive.element; | 267 ImportElement importElem = directive.element; |
| 259 if (importElem != null && importElem.importedLibrary != null) { | 268 if (importElem != null && importElem.importedLibrary != null) { |
| 260 if (directive.prefix == null) { | 269 if (directive.prefix == null) { |
| 261 Namespace importNamespace = | 270 Namespace importNamespace = |
| 262 new NamespaceBuilder().createImportNamespaceForDirective(importE
lem); | 271 new NamespaceBuilder().createImportNamespaceForDirective(importE
lem); |
| 263 // Include top level elements | 272 // Include top level elements |
| 264 importNamespace.definedNames.forEach((String name, Element elem) { | 273 importNamespace.definedNames.forEach((String name, Element elem) { |
| 265 if (elem is ClassElement) { | 274 if (elem is ClassElement) { |
| 266 classMap[name] = elem; | 275 classMap[name] = elem; |
| 267 _addElementSuggestion(elem, CompletionRelevance.DEFAULT); | |
| 268 } else if (!typesOnly) { | |
| 269 _addElementSuggestion(elem, CompletionRelevance.DEFAULT); | |
| 270 } | 276 } |
| 277 _addElementSuggestion(elem, typesOnly, excludeVoidReturn, Comple
tionRelevance.DEFAULT); |
| 271 }); | 278 }); |
| 272 } else { | 279 } else { |
| 273 // Exclude elements from prefixed imports | 280 // Exclude elements from prefixed imports |
| 274 // because they are provided by InvocationComputer | 281 // because they are provided by InvocationComputer |
| 275 excludedLibs.add(importElem.importedLibrary); | 282 excludedLibs.add(importElem.importedLibrary); |
| 276 _addLibraryPrefixSuggestion(importElem); | 283 _addLibraryPrefixSuggestion(importElem); |
| 277 } | 284 } |
| 278 } | 285 } |
| 279 } | 286 } |
| 280 }); | 287 }); |
| 281 | 288 |
| 282 // Include implicitly imported dart:core elements | 289 // Include implicitly imported dart:core elements |
| 283 Source coreUri = request.context.sourceFactory.forUri('dart:core'); | 290 Source coreUri = request.context.sourceFactory.forUri('dart:core'); |
| 284 LibraryElement coreLib = request.context.getLibraryElement(coreUri); | 291 LibraryElement coreLib = request.context.getLibraryElement(coreUri); |
| 285 Namespace coreNamespace = | 292 Namespace coreNamespace = |
| 286 new NamespaceBuilder().createPublicNamespaceForLibrary(coreLib); | 293 new NamespaceBuilder().createPublicNamespaceForLibrary(coreLib); |
| 287 coreNamespace.definedNames.forEach((String name, Element elem) { | 294 coreNamespace.definedNames.forEach((String name, Element elem) { |
| 288 if (elem is ClassElement) { | 295 if (elem is ClassElement) { |
| 289 classMap[name] = elem; | 296 classMap[name] = elem; |
| 290 _addElementSuggestion(elem, CompletionRelevance.DEFAULT); | |
| 291 } else if (!typesOnly) { | |
| 292 _addElementSuggestion(elem, CompletionRelevance.DEFAULT); | |
| 293 } | 297 } |
| 298 _addElementSuggestion(elem, typesOnly, excludeVoidReturn, CompletionRele
vance.DEFAULT); |
| 294 }); | 299 }); |
| 295 | 300 |
| 296 // Build a list of inherited types that are imported | 301 // Build a list of inherited types that are imported |
| 297 // and include any inherited imported members | 302 // and include any inherited imported members |
| 298 var classDecl = node.getAncestor((p) => p is ClassDeclaration); | 303 var classDecl = node.getAncestor((p) => p is ClassDeclaration); |
| 299 if (classDecl is ClassDeclaration) { | 304 if (classDecl is ClassDeclaration) { |
| 300 List<String> inheritedTypes = new List<String>(); | 305 List<String> inheritedTypes = new List<String>(); |
| 301 visitInheritedTypes(classDecl, (ClassDeclaration classDecl) { | 306 visitInheritedTypes(classDecl, (ClassDeclaration classDecl) { |
| 302 // ignored | 307 // ignored |
| 303 }, (String typeName) { | 308 }, (String typeName) { |
| 304 inheritedTypes.add(typeName); | 309 inheritedTypes.add(typeName); |
| 305 }); | 310 }); |
| 306 Set<String> visited = new Set<String>(); | 311 Set<String> visited = new Set<String>(); |
| 307 while (inheritedTypes.length > 0) { | 312 while (inheritedTypes.length > 0) { |
| 308 String name = inheritedTypes.removeLast(); | 313 String name = inheritedTypes.removeLast(); |
| 309 ClassElement elem = classMap[name]; | 314 ClassElement elem = classMap[name]; |
| 310 if (visited.add(name) && elem != null) { | 315 if (visited.add(name) && elem != null) { |
| 311 _addElementSuggestions(elem.accessors); | 316 _addElementSuggestions(elem.accessors, typesOnly, excludeVoidReturn); |
| 312 _addElementSuggestions(elem.methods); | 317 _addElementSuggestions(elem.methods, typesOnly, excludeVoidReturn); |
| 313 elem.allSupertypes.forEach((InterfaceType type) { | 318 elem.allSupertypes.forEach((InterfaceType type) { |
| 314 if (visited.add(type.name)) { | 319 if (visited.add(type.name)) { |
| 315 _addElementSuggestions(type.accessors); | 320 _addElementSuggestions(type.accessors, typesOnly, excludeVoidRetur
n); |
| 316 _addElementSuggestions(type.methods); | 321 _addElementSuggestions(type.methods, typesOnly, excludeVoidReturn)
; |
| 317 } | 322 } |
| 318 }); | 323 }); |
| 319 } | 324 } |
| 320 } | 325 } |
| 321 } | 326 } |
| 322 | 327 |
| 323 // Add non-imported elements as low relevance | 328 // Add non-imported elements as low relevance |
| 324 var future = request.searchEngine.searchTopLevelDeclarations(''); | 329 var future = request.searchEngine.searchTopLevelDeclarations(''); |
| 325 return future.then((List<SearchMatch> matches) { | 330 return future.then((List<SearchMatch> matches) { |
| 326 Set<String> completionSet = new Set<String>(); | 331 Set<String> completionSet = new Set<String>(); |
| 327 request.suggestions.forEach((CompletionSuggestion suggestion) { | 332 request.suggestions.forEach((CompletionSuggestion suggestion) { |
| 328 completionSet.add(suggestion.completion); | 333 completionSet.add(suggestion.completion); |
| 329 }); | 334 }); |
| 330 matches.forEach((SearchMatch match) { | 335 matches.forEach((SearchMatch match) { |
| 331 if (match.kind == MatchKind.DECLARATION) { | 336 if (match.kind == MatchKind.DECLARATION) { |
| 332 Element element = match.element; | 337 Element element = match.element; |
| 333 if (element.isPublic && | 338 if (element.isPublic && |
| 334 !excludedLibs.contains(element.library) && | 339 !excludedLibs.contains(element.library) && |
| 335 !completionSet.contains(element.displayName)) { | 340 !completionSet.contains(element.displayName)) { |
| 336 if (!typesOnly || element is ClassElement) { | 341 if (!typesOnly || element is ClassElement) { |
| 337 _addElementSuggestion(element, CompletionRelevance.LOW); | 342 _addElementSuggestion(element, typesOnly, excludeVoidReturn, Compl
etionRelevance.LOW); |
| 338 } | 343 } |
| 339 } | 344 } |
| 340 } | 345 } |
| 341 }); | 346 }); |
| 342 return true; | 347 return true; |
| 343 }); | 348 }); |
| 344 } | 349 } |
| 345 | 350 |
| 346 void _addLibraryPrefixSuggestion(ImportElement importElem) { | 351 void _addLibraryPrefixSuggestion(ImportElement importElem) { |
| 347 String completion = importElem.prefix.displayName; | 352 String completion = importElem.prefix.displayName; |
| 348 if (completion != null && completion.length > 0) { | 353 if (completion != null && completion.length > 0) { |
| 349 CompletionSuggestion suggestion = new CompletionSuggestion( | 354 CompletionSuggestion suggestion = new CompletionSuggestion( |
| 350 CompletionSuggestionKind.LIBRARY_PREFIX, | 355 CompletionSuggestionKind.LIBRARY_PREFIX, |
| 351 CompletionRelevance.DEFAULT, | 356 CompletionRelevance.DEFAULT, |
| 352 completion, | 357 completion, |
| 353 completion.length, | 358 completion.length, |
| 354 0, | 359 0, |
| 355 importElem.isDeprecated, | 360 importElem.isDeprecated, |
| 356 false); | 361 false); |
| 357 LibraryElement lib = importElem.importedLibrary; | 362 LibraryElement lib = importElem.importedLibrary; |
| 358 if (lib != null) { | 363 if (lib != null) { |
| 359 suggestion.element = newElement_fromEngine(lib); | 364 suggestion.element = newElement_fromEngine(lib); |
| 360 } | 365 } |
| 361 request.suggestions.add(suggestion); | 366 request.suggestions.add(suggestion); |
| 362 } | 367 } |
| 363 } | 368 } |
| 364 } | 369 } |
| OLD | NEW |