| OLD | NEW |
| 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2017, 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.src.completion.statement; | 5 library services.src.completion.statement; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 | 8 |
| 9 import 'package:analysis_server/protocol/protocol_generated.dart'; | 9 import 'package:analysis_server/protocol/protocol_generated.dart'; |
| 10 import 'package:analysis_server/src/protocol_server.dart' hide Element; | 10 import 'package:analysis_server/src/protocol_server.dart' hide Element; |
| (...skipping 15 matching lines...) Expand all Loading... |
| 26 /** | 26 /** |
| 27 * An enumeration of possible statement completion kinds. | 27 * An enumeration of possible statement completion kinds. |
| 28 */ | 28 */ |
| 29 class DartStatementCompletion { | 29 class DartStatementCompletion { |
| 30 static const NO_COMPLETION = | 30 static const NO_COMPLETION = |
| 31 const StatementCompletionKind('No_COMPLETION', 'No completion available'); | 31 const StatementCompletionKind('No_COMPLETION', 'No completion available'); |
| 32 static const SIMPLE_ENTER = const StatementCompletionKind( | 32 static const SIMPLE_ENTER = const StatementCompletionKind( |
| 33 'SIMPLE_ENTER', "Insert a newline at the end of the current line"); | 33 'SIMPLE_ENTER', "Insert a newline at the end of the current line"); |
| 34 static const SIMPLE_SEMICOLON = const StatementCompletionKind( | 34 static const SIMPLE_SEMICOLON = const StatementCompletionKind( |
| 35 'SIMPLE_SEMICOLON', "Add a semicolon and newline"); | 35 'SIMPLE_SEMICOLON', "Add a semicolon and newline"); |
| 36 static const COMPLETE_CLASS_DECLARATION = const StatementCompletionKind( |
| 37 'COMPLETE_CLASS_DECLARATION', "Complete class declaration"); |
| 36 static const COMPLETE_CONTROL_FLOW_BLOCK = const StatementCompletionKind( | 38 static const COMPLETE_CONTROL_FLOW_BLOCK = const StatementCompletionKind( |
| 37 'COMPLETE_CONTROL_FLOW_BLOCK', "Complete control flow block"); | 39 'COMPLETE_CONTROL_FLOW_BLOCK', "Complete control flow block"); |
| 38 static const COMPLETE_DO_STMT = const StatementCompletionKind( | 40 static const COMPLETE_DO_STMT = const StatementCompletionKind( |
| 39 'COMPLETE_DO_STMT', "Complete do-statement"); | 41 'COMPLETE_DO_STMT', "Complete do-statement"); |
| 40 static const COMPLETE_IF_STMT = const StatementCompletionKind( | 42 static const COMPLETE_IF_STMT = const StatementCompletionKind( |
| 41 'COMPLETE_IF_STMT', "Complete if-statement"); | 43 'COMPLETE_IF_STMT', "Complete if-statement"); |
| 42 static const COMPLETE_FOR_STMT = const StatementCompletionKind( | 44 static const COMPLETE_FOR_STMT = const StatementCompletionKind( |
| 43 'COMPLETE_FOR_STMT', "Complete for-statement"); | 45 'COMPLETE_FOR_STMT', "Complete for-statement"); |
| 44 static const COMPLETE_FOR_EACH_STMT = const StatementCompletionKind( | 46 static const COMPLETE_FOR_EACH_STMT = const StatementCompletionKind( |
| 45 'COMPLETE_FOR_EACH_STMT', "Complete for-each-statement"); | 47 'COMPLETE_FOR_EACH_STMT', "Complete for-each-statement"); |
| 48 static const COMPLETE_FUNCTION_DECLARATION = const StatementCompletionKind( |
| 49 'COMPLETE_FUNCTION_DECLARATION', "Complete function declaration"); |
| 46 static const COMPLETE_SWITCH_STMT = const StatementCompletionKind( | 50 static const COMPLETE_SWITCH_STMT = const StatementCompletionKind( |
| 47 'COMPLETE_SWITCH_STMT', "Complete switch-statement"); | 51 'COMPLETE_SWITCH_STMT', "Complete switch-statement"); |
| 48 static const COMPLETE_TRY_STMT = const StatementCompletionKind( | 52 static const COMPLETE_TRY_STMT = const StatementCompletionKind( |
| 49 'COMPLETE_TRY_STMT', "Complete try-statement"); | 53 'COMPLETE_TRY_STMT', "Complete try-statement"); |
| 54 static const COMPLETE_VARIABLE_DECLARATION = const StatementCompletionKind( |
| 55 'COMPLETE_VARIABLE_DECLARATION', "Complete variable declaration"); |
| 50 static const COMPLETE_WHILE_STMT = const StatementCompletionKind( | 56 static const COMPLETE_WHILE_STMT = const StatementCompletionKind( |
| 51 'COMPLETE_WHILE_STMT', "Complete while-statement"); | 57 'COMPLETE_WHILE_STMT', "Complete while-statement"); |
| 52 } | 58 } |
| 53 | 59 |
| 54 /** | 60 /** |
| 55 * A description of a statement completion. | 61 * A description of a statement completion. |
| 56 * | 62 * |
| 57 * Clients may not extend, implement or mix-in this class. | 63 * Clients may not extend, implement or mix-in this class. |
| 58 */ | 64 */ |
| 59 class StatementCompletion { | 65 class StatementCompletion { |
| (...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 161 CompilationUnit get unit => statementContext.unit; | 167 CompilationUnit get unit => statementContext.unit; |
| 162 | 168 |
| 163 CompilationUnitElement get unitElement => statementContext.unitElement; | 169 CompilationUnitElement get unitElement => statementContext.unitElement; |
| 164 | 170 |
| 165 Future<StatementCompletion> compute() async { | 171 Future<StatementCompletion> compute() async { |
| 166 // If the source was changed between the constructor and running | 172 // If the source was changed between the constructor and running |
| 167 // this asynchronous method, it is not safe to use the unit. | 173 // this asynchronous method, it is not safe to use the unit. |
| 168 if (analysisContext.getModificationStamp(source) != fileStamp) { | 174 if (analysisContext.getModificationStamp(source) != fileStamp) { |
| 169 return NO_COMPLETION; | 175 return NO_COMPLETION; |
| 170 } | 176 } |
| 171 node = new NodeLocator(selectionOffset).searchWithin(unit); | 177 node = _selectedNode(); |
| 172 if (node == null) { | 178 if (node == null) { |
| 173 return NO_COMPLETION; | 179 return NO_COMPLETION; |
| 174 } | 180 } |
| 175 // TODO(messick): This needs to work for declarations. | 181 node = node |
| 176 AstNode newNode = node.getAncestor((n) => n is Statement); | 182 .getAncestor((n) => n is Statement || _isNonStatementDeclaration(n)); |
| 177 if (newNode is Block) { | 183 if (node == null) { |
| 178 Block blockNode = newNode; | 184 return _complete_simpleEnter() ? completion : NO_COMPLETION; |
| 185 } |
| 186 if (node is Block) { |
| 187 Block blockNode = node; |
| 179 if (blockNode.statements.isNotEmpty) { | 188 if (blockNode.statements.isNotEmpty) { |
| 180 node = blockNode.statements.last; | 189 node = blockNode.statements.last; |
| 181 } else { | |
| 182 node = newNode; | |
| 183 } | 190 } |
| 184 } else { | |
| 185 node = newNode; | |
| 186 } | 191 } |
| 187 if (_isEmptyStatement(node)) { | 192 if (_isEmptyStatement(node)) { |
| 188 node = node.parent; | 193 node = node.parent; |
| 189 } | 194 } |
| 190 for (engine.AnalysisError error in statementContext.errors) { | 195 for (engine.AnalysisError error in statementContext.errors) { |
| 191 if (error.offset >= node.offset && | 196 if (error.offset >= node.offset && |
| 192 error.offset <= node.offset + node.length) { | 197 error.offset <= node.offset + node.length) { |
| 193 if (error.errorCode is! HintCode) { | 198 if (error.errorCode is! HintCode) { |
| 194 errors.add(error); | 199 errors.add(error); |
| 195 } | 200 } |
| 196 } | 201 } |
| 197 } | 202 } |
| 198 | 203 |
| 199 if (errors.isEmpty) { | 204 if (node is Statement) { |
| 200 if (_complete_controlFlowBlock() || _complete_simpleEnter()) { | 205 if (errors.isEmpty) { |
| 201 return completion; | 206 if (_complete_ifStatement() || |
| 207 _complete_forStatement() || |
| 208 _complete_forEachStatement() || |
| 209 _complete_whileStatement() || |
| 210 _complete_controlFlowBlock()) { |
| 211 return completion; |
| 212 } |
| 213 } else { |
| 214 if (_complete_ifStatement() || |
| 215 _complete_doStatement() || |
| 216 _complete_forStatement() || |
| 217 _complete_forEachStatement() || |
| 218 _complete_functionDeclarationStatement() || |
| 219 _complete_switchStatement() || |
| 220 _complete_tryStatement() || |
| 221 _complete_whileStatement() || |
| 222 _complete_controlFlowBlock() || |
| 223 _complete_simpleSemicolon() || |
| 224 _complete_methodCall()) { |
| 225 return completion; |
| 226 } |
| 202 } | 227 } |
| 203 } else { | 228 } else if (node is Declaration) { |
| 204 if (_complete_ifStatement() || | 229 if (errors.isNotEmpty) { |
| 205 _complete_doStatement() || | 230 if (_complete_classDeclaration() || |
| 206 _complete_forStatement() || | 231 _complete_functionDeclaration() || |
| 207 _complete_forEachStatement() || | 232 _complete_variableDeclaration()) { |
| 208 _complete_switchStatement() || | 233 return completion; |
| 209 _complete_tryStatement() || | 234 } |
| 210 _complete_whileStatement() || | |
| 211 _complete_controlFlowBlock() || | |
| 212 _complete_simpleSemicolon() || | |
| 213 _complete_simpleEnter()) { | |
| 214 return completion; | |
| 215 } | 235 } |
| 216 } | 236 } |
| 237 if (_complete_simpleEnter()) { |
| 238 return completion; |
| 239 } |
| 217 return NO_COMPLETION; | 240 return NO_COMPLETION; |
| 218 } | 241 } |
| 219 | 242 |
| 220 void _addInsertEdit(int offset, String text) { | 243 void _addInsertEdit(int offset, String text) { |
| 221 SourceEdit edit = new SourceEdit(offset, 0, text); | 244 SourceEdit edit = new SourceEdit(offset, 0, text); |
| 222 doSourceChange_addElementEdit(change, unitElement, edit); | 245 doSourceChange_addElementEdit(change, unitElement, edit); |
| 223 } | 246 } |
| 224 | 247 |
| 225 void _addReplaceEdit(SourceRange range, String text) { | 248 void _addReplaceEdit(SourceRange range, String text) { |
| 226 SourceEdit edit = new SourceEdit(range.offset, range.length, text); | 249 SourceEdit edit = new SourceEdit(range.offset, range.length, text); |
| (...skipping 28 matching lines...) Expand all Loading... |
| 255 } | 278 } |
| 256 | 279 |
| 257 String _baseNodeText(AstNode astNode) { | 280 String _baseNodeText(AstNode astNode) { |
| 258 String text = utils.getNodeText(astNode); | 281 String text = utils.getNodeText(astNode); |
| 259 if (text.endsWith(eol)) { | 282 if (text.endsWith(eol)) { |
| 260 text = text.substring(0, text.length - eol.length); | 283 text = text.substring(0, text.length - eol.length); |
| 261 } | 284 } |
| 262 return text; | 285 return text; |
| 263 } | 286 } |
| 264 | 287 |
| 288 bool _complete_classDeclaration() { |
| 289 if (node is! ClassDeclaration) { |
| 290 return false; |
| 291 } |
| 292 ClassDeclaration decl = node; |
| 293 if (decl.leftBracket.isSynthetic && errors.length == 1) { |
| 294 // The space before the left brace is assumed to exist, even if it does no
t. |
| 295 SourceBuilder sb = new SourceBuilder(file, decl.end - 1); |
| 296 sb.append(' '); |
| 297 _appendEmptyBraces(sb, true); |
| 298 _insertBuilder(sb); |
| 299 _setCompletion(DartStatementCompletion.COMPLETE_CLASS_DECLARATION); |
| 300 return true; |
| 301 } |
| 302 return false; |
| 303 } |
| 304 |
| 265 bool _complete_controlFlowBlock() { | 305 bool _complete_controlFlowBlock() { |
| 266 Expression expr = (node is ExpressionStatement) | 306 Expression expr = (node is ExpressionStatement) |
| 267 ? (node as ExpressionStatement).expression | 307 ? (node as ExpressionStatement).expression |
| 268 : (node is ReturnStatement | 308 : (node is ReturnStatement |
| 269 ? (node as ReturnStatement).expression | 309 ? (node as ReturnStatement).expression |
| 270 : null); | 310 : null); |
| 271 if (!(node is ReturnStatement || expr is ThrowExpression)) { | 311 if (!(node is ReturnStatement || expr is ThrowExpression)) { |
| 272 return false; | 312 return false; |
| 273 } | 313 } |
| 274 if (node.parent is! Block) { | 314 if (node.parent is! Block) { |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 315 } | 355 } |
| 316 | 356 |
| 317 bool _complete_doStatement() { | 357 bool _complete_doStatement() { |
| 318 if (node is! DoStatement) { | 358 if (node is! DoStatement) { |
| 319 return false; | 359 return false; |
| 320 } | 360 } |
| 321 DoStatement statement = node; | 361 DoStatement statement = node; |
| 322 SourceBuilder sb = _sourceBuilderAfterKeyword(statement.doKeyword); | 362 SourceBuilder sb = _sourceBuilderAfterKeyword(statement.doKeyword); |
| 323 bool hasWhileKeyword = statement.whileKeyword.lexeme == "while"; | 363 bool hasWhileKeyword = statement.whileKeyword.lexeme == "while"; |
| 324 int exitDelta = 0; | 364 int exitDelta = 0; |
| 325 if (statement.body is EmptyStatement) { | 365 if (!_statementHasValidBody(statement.doKeyword, statement.body)) { |
| 326 String text = utils.getNodeText(statement.body); | 366 String text = utils.getNodeText(statement.body); |
| 327 int delta = 0; | 367 int delta = 0; |
| 328 if (text.startsWith(';')) { | 368 if (text.startsWith(';')) { |
| 329 delta = 1; | 369 delta = 1; |
| 330 _addReplaceEdit(rangeStartLength(statement.body.offset, delta), ''); | 370 _addReplaceEdit(rangeStartLength(statement.body.offset, delta), ''); |
| 331 if (hasWhileKeyword) { | 371 if (hasWhileKeyword) { |
| 332 text = utils.getNodeText(statement); | 372 text = utils.getNodeText(statement); |
| 333 if (text.indexOf(new RegExp(r'do\s*;\s*while')) == 0) { | 373 if (text.indexOf(new RegExp(r'do\s*;\s*while')) == 0) { |
| 334 int end = text.indexOf('while'); | 374 int end = text.indexOf('while'); |
| 335 int start = text.indexOf(';') + 1; | 375 int start = text.indexOf(';') + 1; |
| (...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 423 } else if (_isSyntheticExpression(forNode.iterable)) { | 463 } else if (_isSyntheticExpression(forNode.iterable)) { |
| 424 exitPosition = new Position(file, forNode.rightParenthesis.offset + 1); | 464 exitPosition = new Position(file, forNode.rightParenthesis.offset + 1); |
| 425 src = src.substring(forNode.inKeyword.offset - forNode.offset); | 465 src = src.substring(forNode.inKeyword.offset - forNode.offset); |
| 426 if (src.startsWith(new RegExp(r'in\s*\)'))) { | 466 if (src.startsWith(new RegExp(r'in\s*\)'))) { |
| 427 _addReplaceEdit( | 467 _addReplaceEdit( |
| 428 rangeStartEnd(forNode.inKeyword.offset + forNode.inKeyword.length, | 468 rangeStartEnd(forNode.inKeyword.offset + forNode.inKeyword.length, |
| 429 forNode.rightParenthesis.offset), | 469 forNode.rightParenthesis.offset), |
| 430 ' '); | 470 ' '); |
| 431 } | 471 } |
| 432 } | 472 } |
| 433 if (_isEmptyStatement(forNode.body)) { | 473 if (!_statementHasValidBody(forNode.forKeyword, forNode.body)) { |
| 434 sb.append(' '); | 474 sb.append(' '); |
| 435 _appendEmptyBraces(sb, exitPosition == null); | 475 _appendEmptyBraces(sb, exitPosition == null); |
| 436 } | 476 } |
| 437 _insertBuilder(sb); | 477 _insertBuilder(sb); |
| 438 _setCompletion(DartStatementCompletion.COMPLETE_FOR_EACH_STMT); | 478 _setCompletion(DartStatementCompletion.COMPLETE_FOR_EACH_STMT); |
| 439 return true; | 479 return true; |
| 440 } | 480 } |
| 441 | 481 |
| 442 bool _complete_forStatement() { | 482 bool _complete_forStatement() { |
| 443 if (node is! ForStatement) { | 483 if (node is! ForStatement) { |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 499 _addReplaceEdit(rangeStartLength(start, end), '; '); | 539 _addReplaceEdit(rangeStartLength(start, end), '; '); |
| 500 delta = end - '; '.length; | 540 delta = end - '; '.length; |
| 501 exitPosition = new Position(file, start); | 541 exitPosition = new Position(file, start); |
| 502 } else { | 542 } else { |
| 503 // Not possible; any comment following init is attached to init. | 543 // Not possible; any comment following init is attached to init. |
| 504 exitPosition = _newPosition(forNode.rightParenthesis.offset); | 544 exitPosition = _newPosition(forNode.rightParenthesis.offset); |
| 505 sb = new SourceBuilder(file, forNode.rightParenthesis.offset); | 545 sb = new SourceBuilder(file, forNode.rightParenthesis.offset); |
| 506 } | 546 } |
| 507 } | 547 } |
| 508 } | 548 } |
| 509 if (forNode.body is EmptyStatement) { | 549 if (!_statementHasValidBody(forNode.forKeyword, forNode.body)) { |
| 510 // keywordOnly | 550 // keywordOnly, noError |
| 511 sb.append(' '); | 551 sb.append(' '); |
| 512 _appendEmptyBraces(sb, exitPosition == null); | 552 _appendEmptyBraces(sb, exitPosition == null); |
| 513 } | 553 } |
| 514 if (delta != 0 && exitPosition != null) { | 554 if (delta != 0 && exitPosition != null) { |
| 515 // missingLeftSeparator | 555 // missingLeftSeparator |
| 516 exitPosition = new Position(file, exitPosition.offset - delta); | 556 exitPosition = new Position(file, exitPosition.offset - delta); |
| 517 } | 557 } |
| 518 _insertBuilder(sb); | 558 _insertBuilder(sb); |
| 519 _setCompletion(DartStatementCompletion.COMPLETE_FOR_STMT); | 559 _setCompletion(DartStatementCompletion.COMPLETE_FOR_STMT); |
| 520 return true; | 560 return true; |
| 521 } | 561 } |
| 522 | 562 |
| 563 bool _complete_functionDeclaration() { |
| 564 if (node is! MethodDeclaration && node is! FunctionDeclaration) { |
| 565 return false; |
| 566 } |
| 567 SourceBuilder sb = new SourceBuilder(file, node.end - 1); |
| 568 sb.append(' '); |
| 569 _appendEmptyBraces(sb, true); |
| 570 _insertBuilder(sb); |
| 571 _setCompletion(DartStatementCompletion.COMPLETE_FUNCTION_DECLARATION); |
| 572 return true; |
| 573 } |
| 574 |
| 575 bool _complete_functionDeclarationStatement() { |
| 576 if (node is! FunctionDeclarationStatement) { |
| 577 return false; |
| 578 } |
| 579 var error = _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "';'"); |
| 580 if (error != null) { |
| 581 FunctionDeclarationStatement stmt = node; |
| 582 String src = utils.getNodeText(stmt); |
| 583 int insertOffset = stmt.functionDeclaration.end - 1; |
| 584 if (stmt.functionDeclaration.functionExpression.body |
| 585 is ExpressionFunctionBody) { |
| 586 ExpressionFunctionBody fnb = |
| 587 stmt.functionDeclaration.functionExpression.body; |
| 588 int fnbOffset = fnb.functionDefinition.offset; |
| 589 String fnSrc = src.substring(fnbOffset - stmt.offset); |
| 590 if (!fnSrc.startsWith('=>')) { |
| 591 return false; |
| 592 } |
| 593 int delta = 0; |
| 594 if (fnb.expression.isSynthetic) { |
| 595 if (!fnSrc.startsWith('=> ')) { |
| 596 _addInsertEdit(insertOffset, ' '); |
| 597 delta = 1; |
| 598 } |
| 599 _addInsertEdit(insertOffset, ';'); |
| 600 _appendNewlinePlusIndentAt(insertOffset); |
| 601 } else { |
| 602 delta = 1; |
| 603 _addInsertEdit(insertOffset, ';'); |
| 604 insertOffset = _appendNewlinePlusIndent(); |
| 605 } |
| 606 _setCompletionAt( |
| 607 DartStatementCompletion.SIMPLE_SEMICOLON, insertOffset + delta); |
| 608 return true; |
| 609 } |
| 610 } |
| 611 return false; |
| 612 } |
| 613 |
| 523 bool _complete_ifOrWhileStatement( | 614 bool _complete_ifOrWhileStatement( |
| 524 _KeywordConditionBlockStructure statement, StatementCompletionKind kind) { | 615 _KeywordConditionBlockStructure statement, StatementCompletionKind kind) { |
| 616 if (_statementHasValidBody(statement.keyword, statement.block)) { |
| 617 return false; |
| 618 } |
| 525 SourceBuilder sb = _complete_keywordCondition(statement); | 619 SourceBuilder sb = _complete_keywordCondition(statement); |
| 526 if (sb == null) { | 620 if (sb == null) { |
| 527 return false; | 621 return false; |
| 528 } | 622 } |
| 529 if (statement.block is EmptyStatement) { | 623 sb.append(' '); |
| 530 sb.append(' '); | 624 _appendEmptyBraces(sb, exitPosition == null); |
| 531 _appendEmptyBraces(sb, exitPosition == null); | |
| 532 } | |
| 533 _insertBuilder(sb); | 625 _insertBuilder(sb); |
| 534 _setCompletion(kind); | 626 _setCompletion(kind); |
| 535 return true; | 627 return true; |
| 536 } | 628 } |
| 537 | 629 |
| 538 bool _complete_ifStatement() { | 630 bool _complete_ifStatement() { |
| 539 if (node is! IfStatement) { | 631 if (node is! IfStatement) { |
| 540 return false; | 632 return false; |
| 541 } | 633 } |
| 542 IfStatement ifNode = node; | 634 IfStatement ifNode = node; |
| 543 if (ifNode != null) { | 635 if (ifNode.elseKeyword != null) { |
| 544 if (ifNode.elseKeyword != null) { | 636 return false; |
| 545 return false; | |
| 546 } | |
| 547 var stmt = new _KeywordConditionBlockStructure( | |
| 548 ifNode.ifKeyword, | |
| 549 ifNode.leftParenthesis, | |
| 550 ifNode.condition, | |
| 551 ifNode.rightParenthesis, | |
| 552 ifNode.thenStatement); | |
| 553 return _complete_ifOrWhileStatement( | |
| 554 stmt, DartStatementCompletion.COMPLETE_IF_STMT); | |
| 555 } | 637 } |
| 556 return false; | 638 var stmt = new _KeywordConditionBlockStructure( |
| 639 ifNode.ifKeyword, |
| 640 ifNode.leftParenthesis, |
| 641 ifNode.condition, |
| 642 ifNode.rightParenthesis, |
| 643 ifNode.thenStatement); |
| 644 return _complete_ifOrWhileStatement( |
| 645 stmt, DartStatementCompletion.COMPLETE_IF_STMT); |
| 557 } | 646 } |
| 558 | 647 |
| 559 SourceBuilder _complete_keywordCondition( | 648 SourceBuilder _complete_keywordCondition( |
| 560 _KeywordConditionBlockStructure statement) { | 649 _KeywordConditionBlockStructure statement) { |
| 561 SourceBuilder sb; | 650 SourceBuilder sb; |
| 562 if (statement.leftParenthesis.isSynthetic) { | 651 if (statement.leftParenthesis.isSynthetic) { |
| 563 if (!statement.rightParenthesis.isSynthetic) { | 652 if (!statement.rightParenthesis.isSynthetic) { |
| 564 // Quite unlikely to see this so don't try to fix it. | 653 // Quite unlikely to see this so don't try to fix it. |
| 565 return null; | 654 return null; |
| 566 } | 655 } |
| 567 sb = _sourceBuilderAfterKeyword(statement.keyword); | 656 sb = _sourceBuilderAfterKeyword(statement.keyword); |
| 568 sb.append('('); | 657 sb.append('('); |
| 569 sb.setExitOffset(); | 658 sb.setExitOffset(); |
| 570 sb.append(')'); | 659 sb.append(')'); |
| 571 } else { | 660 } else { |
| 572 if (_isSyntheticExpression(statement.condition)) { | 661 if (_isSyntheticExpression(statement.condition)) { |
| 573 exitPosition = _newPosition(statement.leftParenthesis.offset + 1); | 662 exitPosition = _newPosition(statement.leftParenthesis.offset + 1); |
| 574 sb = new SourceBuilder(file, statement.rightParenthesis.offset + 1); | 663 sb = new SourceBuilder(file, statement.rightParenthesis.offset + 1); |
| 575 } else { | 664 } else { |
| 576 sb = new SourceBuilder(file, statement.rightParenthesis.offset + 1); | 665 sb = new SourceBuilder(file, statement.rightParenthesis.offset + 1); |
| 577 } | 666 } |
| 578 } | 667 } |
| 579 return sb; | 668 return sb; |
| 580 } | 669 } |
| 581 | 670 |
| 671 bool _complete_methodCall() { |
| 672 var parenError = |
| 673 _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "')'"); |
| 674 if (parenError == null) { |
| 675 return false; |
| 676 } |
| 677 AstNode argList = _selectedNode(at: parenError.offset - 1) |
| 678 .getAncestor((n) => n is ArgumentList); |
| 679 if (argList?.getAncestor((n) => n == node) == null) { |
| 680 return false; |
| 681 } |
| 682 int loc = argList.end - 1; |
| 683 int delta = 1; |
| 684 var semicolonError = |
| 685 _findError(ParserErrorCode.EXPECTED_TOKEN, partialMatch: "';'"); |
| 686 if (semicolonError == null) { |
| 687 loc += 1; |
| 688 delta = 0; |
| 689 } |
| 690 _addInsertEdit(loc, ')'); |
| 691 if (semicolonError != null) { |
| 692 _addInsertEdit(loc, ';'); |
| 693 } |
| 694 String indent = utils.getLinePrefix(selectionOffset); |
| 695 int exit = utils.getLineNext(selectionOffset); |
| 696 _addInsertEdit(exit, indent + eol); |
| 697 exit += indent.length + eol.length; |
| 698 |
| 699 _setCompletionAt(DartStatementCompletion.SIMPLE_ENTER, exit + delta); |
| 700 return true; |
| 701 } |
| 702 |
| 582 bool _complete_simpleEnter() { | 703 bool _complete_simpleEnter() { |
| 583 int offset; | 704 int offset; |
| 584 if (!errors.isEmpty) { | 705 if (!errors.isEmpty) { |
| 585 offset = selectionOffset; | 706 offset = selectionOffset; |
| 586 } else { | 707 } else { |
| 587 String indent = utils.getLinePrefix(selectionOffset); | 708 String indent = utils.getLinePrefix(selectionOffset); |
| 588 int loc = utils.getLineNext(selectionOffset); | 709 int loc = utils.getLineNext(selectionOffset); |
| 589 _addInsertEdit(loc, indent + eol); | 710 _addInsertEdit(loc, indent + eol); |
| 590 offset = loc + indent.length + eol.length; | 711 offset = loc + indent.length + eol.length; |
| 591 } | 712 } |
| (...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 741 sb = new SourceBuilder(file, tryNode.finallyKeyword.end); | 862 sb = new SourceBuilder(file, tryNode.finallyKeyword.end); |
| 742 sb.append(' '); | 863 sb.append(' '); |
| 743 _appendEmptyBraces(sb, true); | 864 _appendEmptyBraces(sb, true); |
| 744 _insertBuilder(sb); | 865 _insertBuilder(sb); |
| 745 } | 866 } |
| 746 } | 867 } |
| 747 _setCompletion(DartStatementCompletion.COMPLETE_TRY_STMT); | 868 _setCompletion(DartStatementCompletion.COMPLETE_TRY_STMT); |
| 748 return true; | 869 return true; |
| 749 } | 870 } |
| 750 | 871 |
| 872 bool _complete_variableDeclaration() { |
| 873 if (node is! VariableDeclaration) { |
| 874 return false; |
| 875 } |
| 876 _addInsertEdit(node.end, ';'); |
| 877 exitPosition = new Position(file, _appendNewlinePlusIndentAt(node.end) + 1); |
| 878 _setCompletion(DartStatementCompletion.COMPLETE_VARIABLE_DECLARATION); |
| 879 return true; |
| 880 } |
| 881 |
| 751 bool _complete_whileStatement() { | 882 bool _complete_whileStatement() { |
| 752 if (node is! WhileStatement) { | 883 if (node is! WhileStatement) { |
| 753 return false; | 884 return false; |
| 754 } | 885 } |
| 755 WhileStatement whileNode = node; | 886 WhileStatement whileNode = node; |
| 756 if (whileNode != null) { | 887 if (whileNode != null) { |
| 757 var stmt = new _KeywordConditionBlockStructure( | 888 var stmt = new _KeywordConditionBlockStructure( |
| 758 whileNode.whileKeyword, | 889 whileNode.whileKeyword, |
| 759 whileNode.leftParenthesis, | 890 whileNode.leftParenthesis, |
| 760 whileNode.condition, | 891 whileNode.condition, |
| 761 whileNode.rightParenthesis, | 892 whileNode.rightParenthesis, |
| 762 whileNode.body); | 893 whileNode.body); |
| 763 return _complete_ifOrWhileStatement( | 894 return _complete_ifOrWhileStatement( |
| 764 stmt, DartStatementCompletion.COMPLETE_WHILE_STMT); | 895 stmt, DartStatementCompletion.COMPLETE_WHILE_STMT); |
| 765 } | 896 } |
| 766 return false; | 897 return false; |
| 767 } | 898 } |
| 768 | 899 |
| 769 engine.AnalysisError _findError(ErrorCode code, {partialMatch: null}) { | 900 engine.AnalysisError _findError(ErrorCode code, {partialMatch: null}) { |
| 770 var error = | 901 return errors.firstWhere( |
| 771 errors.firstWhere((err) => err.errorCode == code, orElse: () => null); | 902 (err) => |
| 772 if (error != null) { | 903 err.errorCode == code && |
| 773 if (partialMatch != null) { | 904 (partialMatch == null ? true : err.message.contains(partialMatch)), |
| 774 return error.message.contains(partialMatch) ? error : null; | 905 orElse: () => null); |
| 775 } | |
| 776 return error; | |
| 777 } | |
| 778 return null; | |
| 779 } | 906 } |
| 780 | 907 |
| 781 T _findInvalidElement<T extends AstNode>(NodeList<T> list) { | 908 T _findInvalidElement<T extends AstNode>(NodeList<T> list) { |
| 782 return list.firstWhere( | 909 return list.firstWhere( |
| 783 (catchClause) => | 910 (item) => selectionOffset >= item.offset && selectionOffset <= item.end, |
| 784 selectionOffset >= catchClause.offset && | |
| 785 selectionOffset <= catchClause.end, | |
| 786 orElse: () => null); | 911 orElse: () => null); |
| 787 } | 912 } |
| 788 | 913 |
| 789 LinkedEditGroup _getLinkedPosition(String groupId) { | 914 LinkedEditGroup _getLinkedPosition(String groupId) { |
| 790 LinkedEditGroup group = linkedPositionGroups[groupId]; | 915 LinkedEditGroup group = linkedPositionGroups[groupId]; |
| 791 if (group == null) { | 916 if (group == null) { |
| 792 group = new LinkedEditGroup.empty(); | 917 group = new LinkedEditGroup.empty(); |
| 793 linkedPositionGroups[groupId] = group; | 918 linkedPositionGroups[groupId] = group; |
| 794 } | 919 } |
| 795 return group; | 920 return group; |
| (...skipping 25 matching lines...) Expand all Loading... |
| 821 } | 946 } |
| 822 | 947 |
| 823 bool _isEmptyBlock(AstNode stmt) { | 948 bool _isEmptyBlock(AstNode stmt) { |
| 824 return stmt is Block && stmt.statements.isEmpty; | 949 return stmt is Block && stmt.statements.isEmpty; |
| 825 } | 950 } |
| 826 | 951 |
| 827 bool _isEmptyStatement(AstNode stmt) { | 952 bool _isEmptyStatement(AstNode stmt) { |
| 828 return stmt is EmptyStatement || _isEmptyBlock(stmt); | 953 return stmt is EmptyStatement || _isEmptyBlock(stmt); |
| 829 } | 954 } |
| 830 | 955 |
| 956 bool _isNonStatementDeclaration(AstNode n) { |
| 957 if (n is! Declaration) { |
| 958 return false; |
| 959 } |
| 960 if (n is! VariableDeclaration && n is! FunctionDeclaration) { |
| 961 return true; |
| 962 } |
| 963 AstNode p = n.parent; |
| 964 return p is! Statement && p?.parent is! Statement; |
| 965 } |
| 966 |
| 831 bool _isSyntheticExpression(Expression expr) { | 967 bool _isSyntheticExpression(Expression expr) { |
| 832 return expr is SimpleIdentifier && expr.isSynthetic; | 968 return expr is SimpleIdentifier && expr.isSynthetic; |
| 833 } | 969 } |
| 834 | 970 |
| 835 Position _newPosition(int offset) { | 971 Position _newPosition(int offset) { |
| 836 return new Position(file, offset); | 972 return new Position(file, offset); |
| 837 } | 973 } |
| 838 | 974 |
| 975 AstNode _selectedNode({int at: null}) => |
| 976 new NodeLocator(at == null ? selectionOffset : at).searchWithin(unit); |
| 977 |
| 839 void _setCompletion(StatementCompletionKind kind, [List args]) { | 978 void _setCompletion(StatementCompletionKind kind, [List args]) { |
| 840 assert(exitPosition != null); | 979 assert(exitPosition != null); |
| 841 change.selection = exitPosition; | 980 change.selection = exitPosition; |
| 842 change.message = formatList(kind.message, args); | 981 change.message = formatList(kind.message, args); |
| 843 linkedPositionGroups.values | 982 linkedPositionGroups.values |
| 844 .forEach((group) => change.addLinkedEditGroup(group)); | 983 .forEach((group) => change.addLinkedEditGroup(group)); |
| 845 completion = new StatementCompletion(kind, change); | 984 completion = new StatementCompletion(kind, change); |
| 846 } | 985 } |
| 847 | 986 |
| 848 void _setCompletionAt(StatementCompletionKind kind, int offset, [List args]) { | 987 void _setCompletionAt(StatementCompletionKind kind, int offset, [List args]) { |
| 849 exitPosition = _newPosition(offset); | 988 exitPosition = _newPosition(offset); |
| 850 _setCompletion(kind, args); | 989 _setCompletion(kind, args); |
| 851 } | 990 } |
| 852 | 991 |
| 853 SourceBuilder _sourceBuilderAfterKeyword(Token keyword) { | 992 SourceBuilder _sourceBuilderAfterKeyword(Token keyword) { |
| 854 SourceBuilder sb; | 993 SourceBuilder sb; |
| 855 String text = _baseNodeText(node); | 994 String text = _baseNodeText(node); |
| 856 text = text.substring(keyword.offset - node.offset); | 995 text = text.substring(keyword.offset - node.offset); |
| 857 int len = keyword.length; | 996 int len = keyword.length; |
| 858 if (text.length == len || | 997 if (text.length == len || |
| 859 !text.substring(len, len + 1).contains(new RegExp(r'\s'))) { | 998 !text.substring(len, len + 1).contains(new RegExp(r'\s'))) { |
| 860 sb = new SourceBuilder(file, keyword.offset + len); | 999 sb = new SourceBuilder(file, keyword.offset + len); |
| 861 sb.append(' '); | 1000 sb.append(' '); |
| 862 } else { | 1001 } else { |
| 863 sb = new SourceBuilder(file, keyword.offset + len + 1); | 1002 sb = new SourceBuilder(file, keyword.offset + len + 1); |
| 864 } | 1003 } |
| 865 return sb; | 1004 return sb; |
| 866 } | 1005 } |
| 1006 |
| 1007 bool _statementHasValidBody(Token keyword, Statement body) { |
| 1008 // A "valid" body is either a non-synthetic block or a single statement |
| 1009 // on the same line as the parent statement, similar to dart_style. |
| 1010 if (body.isSynthetic) { |
| 1011 return false; |
| 1012 } |
| 1013 if (body is Block) { |
| 1014 Block block = body; |
| 1015 return (!(block.leftBracket.isSynthetic)); |
| 1016 } |
| 1017 return (lineInfo.getLocation(keyword.offset) == |
| 1018 lineInfo.getLocation(body.offset)); |
| 1019 } |
| 867 } | 1020 } |
| 868 | 1021 |
| 869 // Encapsulate common structure of if-statement and while-statement. | 1022 // Encapsulate common structure of if-statement and while-statement. |
| 870 class _KeywordConditionBlockStructure { | 1023 class _KeywordConditionBlockStructure { |
| 871 final Token keyword; | 1024 final Token keyword; |
| 872 final Token leftParenthesis, rightParenthesis; | 1025 final Token leftParenthesis, rightParenthesis; |
| 873 final Expression condition; | 1026 final Expression condition; |
| 874 final Statement block; | 1027 final Statement block; |
| 875 | 1028 |
| 876 _KeywordConditionBlockStructure(this.keyword, this.leftParenthesis, | 1029 _KeywordConditionBlockStructure(this.keyword, this.leftParenthesis, |
| 877 this.condition, this.rightParenthesis, this.block); | 1030 this.condition, this.rightParenthesis, this.block); |
| 878 | 1031 |
| 879 int get offset => keyword.offset; | 1032 int get offset => keyword.offset; |
| 880 } | 1033 } |
| OLD | NEW |