| 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/plugin/protocol/protocol.dart'; | 9 import 'package:analysis_server/plugin/protocol/protocol.dart'; |
| 10 import 'package:analysis_server/src/protocol_server.dart' hide Element; | 10 import 'package:analysis_server/src/protocol_server.dart' hide Element; |
| (...skipping 21 matching lines...) Expand all Loading... |
| 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_DO_STMT = const StatementCompletionKind( | 36 static const COMPLETE_DO_STMT = const StatementCompletionKind( |
| 37 'COMPLETE_DO_STMT', "Complete do-statement"); | 37 'COMPLETE_DO_STMT', "Complete do-statement"); |
| 38 static const COMPLETE_IF_STMT = const StatementCompletionKind( | 38 static const COMPLETE_IF_STMT = const StatementCompletionKind( |
| 39 'COMPLETE_IF_STMT', "Complete if-statement"); | 39 'COMPLETE_IF_STMT', "Complete if-statement"); |
| 40 static const COMPLETE_FOR_STMT = const StatementCompletionKind( | 40 static const COMPLETE_FOR_STMT = const StatementCompletionKind( |
| 41 'COMPLETE_FOR_STMT', "Complete for-statement"); | 41 'COMPLETE_FOR_STMT', "Complete for-statement"); |
| 42 static const COMPLETE_FOR_EACH_STMT = const StatementCompletionKind( |
| 43 'COMPLETE_FOR_EACH_STMT', "Complete for-each-statement"); |
| 44 static const COMPLETE_SWITCH_STMT = const StatementCompletionKind( |
| 45 'COMPLETE_SWITCH_STMT', "Complete switch-statement"); |
| 42 static const COMPLETE_WHILE_STMT = const StatementCompletionKind( | 46 static const COMPLETE_WHILE_STMT = const StatementCompletionKind( |
| 43 'COMPLETE_WHILE_STMT', "Complete while-statement"); | 47 'COMPLETE_WHILE_STMT', "Complete while-statement"); |
| 44 } | 48 } |
| 45 | 49 |
| 46 /** | 50 /** |
| 47 * A description of a statement completion. | 51 * A description of a statement completion. |
| 48 * | 52 * |
| 49 * Clients may not extend, implement or mix-in this class. | 53 * Clients may not extend, implement or mix-in this class. |
| 50 */ | 54 */ |
| 51 class StatementCompletion { | 55 class StatementCompletion { |
| (...skipping 175 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 227 } | 231 } |
| 228 | 232 |
| 229 String _baseNodeText(AstNode astNode) { | 233 String _baseNodeText(AstNode astNode) { |
| 230 String text = utils.getNodeText(astNode); | 234 String text = utils.getNodeText(astNode); |
| 231 if (text.endsWith(eol)) { | 235 if (text.endsWith(eol)) { |
| 232 text = text.substring(0, text.length - eol.length); | 236 text = text.substring(0, text.length - eol.length); |
| 233 } | 237 } |
| 234 return text; | 238 return text; |
| 235 } | 239 } |
| 236 | 240 |
| 241 bool _complete_controlFlowBlock() { |
| 242 //TODO(messick) Implement _complete_controlFlowBlock |
| 243 // Use statement completion to move the cursor to a new line outside the |
| 244 // current block. The statement has no errors in this case. Used to jump |
| 245 // out of do/for/if/while blocks. |
| 246 //TODO(messick) Move has-errors checking into dispatch method. |
| 247 return false; |
| 248 } |
| 249 |
| 237 bool _complete_doStatement() { | 250 bool _complete_doStatement() { |
| 238 if (errors.isEmpty || node is! DoStatement) { | 251 if (errors.isEmpty || node is! DoStatement) { |
| 239 return false; | 252 return false; |
| 240 } | 253 } |
| 241 DoStatement statement = node; | 254 DoStatement statement = node; |
| 242 SourceBuilder sb = _sourceBuilderAfterKeyword(statement.doKeyword); | 255 SourceBuilder sb = _sourceBuilderAfterKeyword(statement.doKeyword); |
| 243 bool hasWhileKeyword = statement.whileKeyword.lexeme == "while"; | 256 bool hasWhileKeyword = statement.whileKeyword.lexeme == "while"; |
| 244 int exitDelta = 0; | 257 int exitDelta = 0; |
| 245 if (statement.body is EmptyStatement) { | 258 if (statement.body is EmptyStatement) { |
| 246 String text = utils.getNodeText(statement.body); | 259 String text = utils.getNodeText(statement.body); |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 304 _insertBuilder(sb); | 317 _insertBuilder(sb); |
| 305 if (exitDelta != 0) { | 318 if (exitDelta != 0) { |
| 306 exitPosition = | 319 exitPosition = |
| 307 new Position(exitPosition.file, exitPosition.offset + exitDelta); | 320 new Position(exitPosition.file, exitPosition.offset + exitDelta); |
| 308 } | 321 } |
| 309 _setCompletion(DartStatementCompletion.COMPLETE_DO_STMT); | 322 _setCompletion(DartStatementCompletion.COMPLETE_DO_STMT); |
| 310 return true; | 323 return true; |
| 311 } | 324 } |
| 312 | 325 |
| 313 bool _complete_forEachStatement() { | 326 bool _complete_forEachStatement() { |
| 314 // TODO(messick) Implement _complete_forEachStatement | 327 if (errors.isEmpty || node is! ForEachStatement) { |
| 315 return false; | 328 return false; |
| 329 } |
| 330 ForEachStatement forNode = node; |
| 331 if (forNode.inKeyword.isSynthetic) { |
| 332 return false; // Can't happen -- would be parsed as a for-statement. |
| 333 } |
| 334 SourceBuilder sb = |
| 335 new SourceBuilder(file, forNode.rightParenthesis.offset + 1); |
| 336 AstNode name = forNode.identifier; |
| 337 name ??= forNode.loopVariable; |
| 338 String src = utils.getNodeText(forNode); |
| 339 if (name == null) { |
| 340 exitPosition = new Position(file, forNode.leftParenthesis.offset + 1); |
| 341 src = src.substring(forNode.leftParenthesis.offset - forNode.offset); |
| 342 if (src.startsWith(new RegExp(r'\(\s*in\s*\)'))) { |
| 343 _addReplaceEdit( |
| 344 rangeStartEnd(forNode.leftParenthesis.offset + 1, |
| 345 forNode.rightParenthesis.offset), |
| 346 ' in '); |
| 347 } else if (src.startsWith(new RegExp(r'\(\s*in'))) { |
| 348 _addReplaceEdit( |
| 349 rangeStartEnd( |
| 350 forNode.leftParenthesis.offset + 1, forNode.inKeyword.offset), |
| 351 ' '); |
| 352 } |
| 353 } else if (_isSyntheticExpression(forNode.iterable)) { |
| 354 exitPosition = new Position(file, forNode.rightParenthesis.offset + 1); |
| 355 src = src.substring(forNode.inKeyword.offset - forNode.offset); |
| 356 if (src.startsWith(new RegExp(r'in\s*\)'))) { |
| 357 _addReplaceEdit( |
| 358 rangeStartEnd(forNode.inKeyword.offset + forNode.inKeyword.length, |
| 359 forNode.rightParenthesis.offset), |
| 360 ' '); |
| 361 } |
| 362 } |
| 363 if (_isEmptyStatement(forNode.body)) { |
| 364 sb.append(' '); |
| 365 _appendEmptyBraces(sb, exitPosition == null); |
| 366 } |
| 367 _insertBuilder(sb); |
| 368 _setCompletion(DartStatementCompletion.COMPLETE_FOR_EACH_STMT); |
| 369 return true; |
| 316 } | 370 } |
| 317 | 371 |
| 318 bool _complete_forStatement() { | 372 bool _complete_forStatement() { |
| 319 if (errors.isEmpty || node is! ForStatement) { | 373 if (errors.isEmpty || node is! ForStatement) { |
| 320 return false; | 374 return false; |
| 321 } | 375 } |
| 322 ForStatement forNode = node; | 376 ForStatement forNode = node; |
| 323 SourceBuilder sb; | 377 SourceBuilder sb; |
| 324 int delta = 0; | 378 int delta = 0; |
| 325 if (forNode.leftParenthesis.isSynthetic) { | 379 if (forNode.leftParenthesis.isSynthetic) { |
| (...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 476 int insertOffset = error.offset + error.length; | 530 int insertOffset = error.offset + error.length; |
| 477 _addInsertEdit(insertOffset, ';'); | 531 _addInsertEdit(insertOffset, ';'); |
| 478 int offset = _appendNewlinePlusIndent() + 1 /* ';' */; | 532 int offset = _appendNewlinePlusIndent() + 1 /* ';' */; |
| 479 _setCompletionAt(DartStatementCompletion.SIMPLE_SEMICOLON, offset); | 533 _setCompletionAt(DartStatementCompletion.SIMPLE_SEMICOLON, offset); |
| 480 return true; | 534 return true; |
| 481 } | 535 } |
| 482 return false; | 536 return false; |
| 483 } | 537 } |
| 484 | 538 |
| 485 bool _complete_switchStatement() { | 539 bool _complete_switchStatement() { |
| 486 // TODO(messick) Implement _complete_switchStatement | 540 if (errors.isEmpty || node is! SwitchStatement) { |
| 487 return false; | 541 return false; |
| 542 } |
| 543 SourceBuilder sb; |
| 544 SwitchStatement switchNode = node; |
| 545 if (switchNode.leftParenthesis.isSynthetic && |
| 546 switchNode.rightParenthesis.isSynthetic) { |
| 547 exitPosition = new Position(file, switchNode.switchKeyword.end + 2); |
| 548 String src = utils.getNodeText(switchNode); |
| 549 if (src |
| 550 .substring(switchNode.switchKeyword.end - switchNode.offset) |
| 551 .startsWith(new RegExp(r'[ \t]+'))) { |
| 552 sb = new SourceBuilder(file, switchNode.switchKeyword.end + 1); |
| 553 } else { |
| 554 sb = new SourceBuilder(file, switchNode.switchKeyword.end); |
| 555 sb.append(' '); |
| 556 } |
| 557 sb.append('()'); |
| 558 } else if (switchNode.leftParenthesis.isSynthetic || |
| 559 switchNode.rightParenthesis.isSynthetic) { |
| 560 return false; |
| 561 } else { |
| 562 sb = new SourceBuilder(file, switchNode.rightParenthesis.offset + 1); |
| 563 if (_isSyntheticExpression(switchNode.expression)) { |
| 564 exitPosition = |
| 565 new Position(file, switchNode.leftParenthesis.offset + 1); |
| 566 } |
| 567 } |
| 568 if (switchNode |
| 569 .leftBracket.isSynthetic /*&& switchNode.rightBracket.isSynthetic*/) { |
| 570 // See https://github.com/dart-lang/sdk/issues/29391 |
| 571 sb.append(' '); |
| 572 _appendEmptyBraces(sb, exitPosition == null); |
| 573 } |
| 574 _insertBuilder(sb); |
| 575 _setCompletion(DartStatementCompletion.COMPLETE_SWITCH_STMT); |
| 576 return true; |
| 488 } | 577 } |
| 489 | 578 |
| 490 bool _complete_tryStatement() { | 579 bool _complete_tryStatement() { |
| 491 // TODO(messick) Implement _complete_tryStatement | 580 // TODO(messick) Implement _complete_tryStatement |
| 492 return false; | 581 return false; |
| 493 } | 582 } |
| 494 | 583 |
| 495 bool _complete_whileStatement() { | 584 bool _complete_whileStatement() { |
| 496 if (errors.isEmpty || node is! WhileStatement) { | 585 if (errors.isEmpty || node is! WhileStatement) { |
| 497 return false; | 586 return false; |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 553 if (exitOffset != null) { | 642 if (exitOffset != null) { |
| 554 exitPosition = _newPosition(exitOffset); | 643 exitPosition = _newPosition(exitOffset); |
| 555 } | 644 } |
| 556 } | 645 } |
| 557 } | 646 } |
| 558 | 647 |
| 559 bool _isEmptyBlock(AstNode stmt) { | 648 bool _isEmptyBlock(AstNode stmt) { |
| 560 return stmt is Block && stmt.statements.isEmpty; | 649 return stmt is Block && stmt.statements.isEmpty; |
| 561 } | 650 } |
| 562 | 651 |
| 652 bool _isEmptyStatement(AstNode stmt) { |
| 653 return stmt is EmptyStatement || _isEmptyBlock(stmt); |
| 654 } |
| 655 |
| 563 bool _isSyntheticExpression(Expression expr) { | 656 bool _isSyntheticExpression(Expression expr) { |
| 564 return expr is SimpleIdentifier && expr.isSynthetic; | 657 return expr is SimpleIdentifier && expr.isSynthetic; |
| 565 } | 658 } |
| 566 | 659 |
| 567 bool _isEmptyStatement(AstNode stmt) { | |
| 568 return stmt is EmptyStatement || _isEmptyBlock(stmt); | |
| 569 } | |
| 570 | |
| 571 Position _newPosition(int offset) { | 660 Position _newPosition(int offset) { |
| 572 return new Position(file, offset); | 661 return new Position(file, offset); |
| 573 } | 662 } |
| 574 | 663 |
| 575 void _setCompletion(StatementCompletionKind kind, [List args]) { | 664 void _setCompletion(StatementCompletionKind kind, [List args]) { |
| 576 assert(exitPosition != null); | 665 assert(exitPosition != null); |
| 577 change.selection = exitPosition; | 666 change.selection = exitPosition; |
| 578 change.message = formatList(kind.message, args); | 667 change.message = formatList(kind.message, args); |
| 579 linkedPositionGroups.values | 668 linkedPositionGroups.values |
| 580 .forEach((group) => change.addLinkedEditGroup(group)); | 669 .forEach((group) => change.addLinkedEditGroup(group)); |
| (...skipping 26 matching lines...) Expand all Loading... |
| 607 final Token keyword; | 696 final Token keyword; |
| 608 final Token leftParenthesis, rightParenthesis; | 697 final Token leftParenthesis, rightParenthesis; |
| 609 final Expression condition; | 698 final Expression condition; |
| 610 final Statement block; | 699 final Statement block; |
| 611 | 700 |
| 612 _KeywordConditionBlockStructure(this.keyword, this.leftParenthesis, | 701 _KeywordConditionBlockStructure(this.keyword, this.leftParenthesis, |
| 613 this.condition, this.rightParenthesis, this.block); | 702 this.condition, this.rightParenthesis, this.block); |
| 614 | 703 |
| 615 int get offset => keyword.offset; | 704 int get offset => keyword.offset; |
| 616 } | 705 } |
| OLD | NEW |