| 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 19 matching lines...) Expand all Loading... |
| 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_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( |
| 41 'COMPLETE_FOR_STMT', "Complete for-statement"); |
| 40 static const COMPLETE_WHILE_STMT = const StatementCompletionKind( | 42 static const COMPLETE_WHILE_STMT = const StatementCompletionKind( |
| 41 'COMPLETE_WHILE_STMT', "Complete while-statement"); | 43 'COMPLETE_WHILE_STMT', "Complete while-statement"); |
| 42 } | 44 } |
| 43 | 45 |
| 44 /** | 46 /** |
| 45 * A description of a statement completion. | 47 * A description of a statement completion. |
| 46 * | 48 * |
| 47 * Clients may not extend, implement or mix-in this class. | 49 * Clients may not extend, implement or mix-in this class. |
| 48 */ | 50 */ |
| 49 class StatementCompletion { | 51 class StatementCompletion { |
| (...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 172 error.offset <= node.offset + node.length) { | 174 error.offset <= node.offset + node.length) { |
| 173 if (error.errorCode is! HintCode) { | 175 if (error.errorCode is! HintCode) { |
| 174 errors.add(error); | 176 errors.add(error); |
| 175 } | 177 } |
| 176 } | 178 } |
| 177 } | 179 } |
| 178 | 180 |
| 179 // TODO(messick) Consider changing (some of) this to a visitor. | 181 // TODO(messick) Consider changing (some of) this to a visitor. |
| 180 if (_complete_ifStatement() || | 182 if (_complete_ifStatement() || |
| 181 _complete_doStatement() || | 183 _complete_doStatement() || |
| 184 _complete_forStatement() || |
| 185 _complete_forEachStatement() || |
| 186 _complete_switchStatement() || |
| 187 _complete_tryStatement() || |
| 182 _complete_whileStatement() || | 188 _complete_whileStatement() || |
| 183 _complete_simpleSemicolon() || | 189 _complete_simpleSemicolon() || |
| 184 _complete_simpleEnter()) { | 190 _complete_simpleEnter()) { |
| 185 return completion; | 191 return completion; |
| 186 } | 192 } |
| 187 return NO_COMPLETION; | 193 return NO_COMPLETION; |
| 188 } | 194 } |
| 189 | 195 |
| 190 void _addIndentEdit(SourceRange range, String oldIndent, String newIndent) { | |
| 191 SourceEdit edit = utils.createIndentEdit(range, oldIndent, newIndent); | |
| 192 doSourceChange_addElementEdit(change, unitElement, edit); | |
| 193 } | |
| 194 | |
| 195 void _addInsertEdit(int offset, String text) { | 196 void _addInsertEdit(int offset, String text) { |
| 196 SourceEdit edit = new SourceEdit(offset, 0, text); | 197 SourceEdit edit = new SourceEdit(offset, 0, text); |
| 197 doSourceChange_addElementEdit(change, unitElement, edit); | 198 doSourceChange_addElementEdit(change, unitElement, edit); |
| 198 } | 199 } |
| 199 | 200 |
| 200 void _addReplaceEdit(SourceRange range, String text) { | 201 void _addReplaceEdit(SourceRange range, String text) { |
| 201 SourceEdit edit = new SourceEdit(range.offset, range.length, text); | 202 SourceEdit edit = new SourceEdit(range.offset, range.length, text); |
| 202 doSourceChange_addElementEdit(change, unitElement, edit); | 203 doSourceChange_addElementEdit(change, unitElement, edit); |
| 203 } | 204 } |
| 204 | 205 |
| (...skipping 26 matching lines...) Expand all Loading... |
| 231 text = text.substring(0, text.length - eol.length); | 232 text = text.substring(0, text.length - eol.length); |
| 232 } | 233 } |
| 233 return text; | 234 return text; |
| 234 } | 235 } |
| 235 | 236 |
| 236 bool _complete_doStatement() { | 237 bool _complete_doStatement() { |
| 237 if (errors.isEmpty || node is! DoStatement) { | 238 if (errors.isEmpty || node is! DoStatement) { |
| 238 return false; | 239 return false; |
| 239 } | 240 } |
| 240 DoStatement statement = node; | 241 DoStatement statement = node; |
| 241 var stmt = new _DoIfWhileStructure( | |
| 242 statement.whileKeyword, | |
| 243 statement.leftParenthesis, | |
| 244 statement.condition, | |
| 245 statement.rightParenthesis, | |
| 246 null); | |
| 247 SourceBuilder sb = _sourceBuilderAfterKeyword(statement.doKeyword); | 242 SourceBuilder sb = _sourceBuilderAfterKeyword(statement.doKeyword); |
| 248 bool hasWhileKeyword = statement.whileKeyword.lexeme == "while"; | 243 bool hasWhileKeyword = statement.whileKeyword.lexeme == "while"; |
| 249 int exitDelta = 0; | 244 int exitDelta = 0; |
| 250 if (statement.body is EmptyStatement) { | 245 if (statement.body is EmptyStatement) { |
| 251 String text = utils.getNodeText(statement.body); | 246 String text = utils.getNodeText(statement.body); |
| 252 int delta = 0; | 247 int delta = 0; |
| 253 if (text.startsWith(';')) { | 248 if (text.startsWith(';')) { |
| 254 delta = 1; | 249 delta = 1; |
| 255 _addReplaceEdit(rangeStartLength(statement.body.offset, delta), ''); | 250 _addReplaceEdit(rangeStartLength(statement.body.offset, delta), ''); |
| 256 if (hasWhileKeyword) { | 251 if (hasWhileKeyword) { |
| (...skipping 12 matching lines...) Expand all Loading... |
| 269 _appendEmptyBraces( | 264 _appendEmptyBraces( |
| 270 sb, !(hasWhileKeyword && _isEmptyExpression(statement.condition))); | 265 sb, !(hasWhileKeyword && _isEmptyExpression(statement.condition))); |
| 271 if (delta != 0) { | 266 if (delta != 0) { |
| 272 exitDelta = sb.length - delta; | 267 exitDelta = sb.length - delta; |
| 273 } | 268 } |
| 274 } else if (_isEmptyBlock(statement.body)) { | 269 } else if (_isEmptyBlock(statement.body)) { |
| 275 sb = new SourceBuilder(sb.file, statement.body.end); | 270 sb = new SourceBuilder(sb.file, statement.body.end); |
| 276 } | 271 } |
| 277 SourceBuilder sb2; | 272 SourceBuilder sb2; |
| 278 if (hasWhileKeyword) { | 273 if (hasWhileKeyword) { |
| 274 var stmt = new _KeywordConditionBlockStructure( |
| 275 statement.whileKeyword, |
| 276 statement.leftParenthesis, |
| 277 statement.condition, |
| 278 statement.rightParenthesis, |
| 279 null); |
| 279 sb2 = _complete_keywordCondition(stmt); | 280 sb2 = _complete_keywordCondition(stmt); |
| 280 if (sb2.length == 0) { | 281 if (sb2.length == 0) { |
| 281 // true if condition is '()' | 282 // true if condition is '()' |
| 282 if (exitPosition != null) { | 283 if (exitPosition != null) { |
| 283 if (statement.semicolon.lexeme.isEmpty) { | 284 if (statement.semicolon.lexeme.isEmpty) { |
| 284 _insertBuilder(sb); | 285 _insertBuilder(sb); |
| 285 sb = new SourceBuilder(file, exitPosition.offset + 1); | 286 sb = new SourceBuilder(file, exitPosition.offset + 1); |
| 286 sb.append(';'); | 287 sb.append(';'); |
| 287 } | 288 } |
| 288 } | 289 } |
| (...skipping 13 matching lines...) Expand all Loading... |
| 302 } | 303 } |
| 303 _insertBuilder(sb); | 304 _insertBuilder(sb); |
| 304 if (exitDelta != 0) { | 305 if (exitDelta != 0) { |
| 305 exitPosition = | 306 exitPosition = |
| 306 new Position(exitPosition.file, exitPosition.offset + exitDelta); | 307 new Position(exitPosition.file, exitPosition.offset + exitDelta); |
| 307 } | 308 } |
| 308 _setCompletion(DartStatementCompletion.COMPLETE_DO_STMT); | 309 _setCompletion(DartStatementCompletion.COMPLETE_DO_STMT); |
| 309 return true; | 310 return true; |
| 310 } | 311 } |
| 311 | 312 |
| 313 bool _complete_forEachStatement() { |
| 314 // TODO(messick) Implement _complete_switchStatement |
| 315 return false; |
| 316 } |
| 317 |
| 318 bool _complete_forStatement() { |
| 319 if (errors.isEmpty || node is! ForStatement) { |
| 320 return false; |
| 321 } |
| 322 ForStatement forNode = node; |
| 323 SourceBuilder sb; |
| 324 int delta = 0; |
| 325 if (forNode.leftParenthesis.lexeme.isEmpty) { |
| 326 if (!forNode.rightParenthesis.lexeme.isEmpty) { |
| 327 return false; |
| 328 } |
| 329 // KeywordOnly (unit test name suffix that exercises this branch) |
| 330 sb = _sourceBuilderAfterKeyword(forNode.forKeyword); |
| 331 sb.append('('); |
| 332 sb.setExitOffset(); |
| 333 sb.append(')'); |
| 334 } else { |
| 335 if (forNode.rightSeparator.lexeme.isNotEmpty) { |
| 336 // Fully-defined init, cond, updaters so nothing more needed here. |
| 337 // EmptyParts |
| 338 sb = new SourceBuilder(file, forNode.rightParenthesis.offset + 1); |
| 339 } else if (forNode.leftSeparator.lexeme.isNotEmpty) { |
| 340 if (_isEmptyExpression(forNode.condition)) { |
| 341 exitPosition = _newPosition(forNode.leftSeparator.offset + 1); |
| 342 String text = utils |
| 343 .getNodeText(forNode) |
| 344 .substring(forNode.leftSeparator.offset - forNode.offset); |
| 345 if (text.startsWith(new RegExp(r';\s*\)'))) { |
| 346 // EmptyCond |
| 347 int end = text.indexOf(')'); |
| 348 sb = new SourceBuilder(file, forNode.leftSeparator.offset); |
| 349 _addReplaceEdit(rangeStartLength(sb.offset, end), '; '); |
| 350 delta = end - '; '.length; |
| 351 } else { |
| 352 // EmptyInitEmptyCond |
| 353 exitPosition = _newPosition(forNode.rightParenthesis.offset); |
| 354 sb = new SourceBuilder(file, forNode.rightParenthesis.offset); |
| 355 } |
| 356 } |
| 357 } else if (_isEmptyExpression(forNode.initialization)) { |
| 358 // EmptyInit |
| 359 exitPosition = _newPosition(forNode.rightParenthesis.offset); |
| 360 sb = new SourceBuilder(file, forNode.rightParenthesis.offset); |
| 361 } else { |
| 362 int start = forNode.condition.offset + forNode.condition.length; |
| 363 String text = |
| 364 utils.getNodeText(forNode).substring(start - forNode.offset); |
| 365 if (text.startsWith(new RegExp(r'\s*\)'))) { |
| 366 // MissingLeftSep |
| 367 int end = text.indexOf(')'); |
| 368 sb = new SourceBuilder(file, start); |
| 369 _addReplaceEdit(rangeStartLength(start, end), '; '); |
| 370 delta = end - '; '.length; |
| 371 exitPosition = new Position(file, start); |
| 372 } else { |
| 373 // Not possible; any comment following init is attached to init. |
| 374 exitPosition = _newPosition(forNode.rightParenthesis.offset); |
| 375 sb = new SourceBuilder(file, forNode.rightParenthesis.offset); |
| 376 } |
| 377 } |
| 378 } |
| 379 if (forNode.body is EmptyStatement) { |
| 380 // KeywordOnly |
| 381 sb.append(' '); |
| 382 _appendEmptyBraces(sb, exitPosition == null); |
| 383 } |
| 384 if (delta != 0 && exitPosition != null) { |
| 385 // MissingLeftSep |
| 386 exitPosition = new Position(file, exitPosition.offset - delta); |
| 387 } |
| 388 _insertBuilder(sb); |
| 389 _setCompletion(DartStatementCompletion.COMPLETE_FOR_STMT); |
| 390 return true; |
| 391 } |
| 392 |
| 312 bool _complete_ifOrWhileStatement( | 393 bool _complete_ifOrWhileStatement( |
| 313 _DoIfWhileStructure statement, StatementCompletionKind kind) { | 394 _KeywordConditionBlockStructure statement, StatementCompletionKind kind) { |
| 314 SourceBuilder sb = _complete_keywordCondition(statement); | 395 SourceBuilder sb = _complete_keywordCondition(statement); |
| 315 if (statement.block is EmptyStatement) { | 396 if (statement.block is EmptyStatement) { |
| 316 sb.append(' '); | 397 sb.append(' '); |
| 317 _appendEmptyBraces(sb, exitPosition == null); | 398 _appendEmptyBraces(sb, exitPosition == null); |
| 318 } | 399 } |
| 319 _insertBuilder(sb); | 400 _insertBuilder(sb); |
| 320 _setCompletion(kind); | 401 _setCompletion(kind); |
| 321 return true; | 402 return true; |
| 322 } | 403 } |
| 323 | 404 |
| 324 bool _complete_ifStatement() { | 405 bool _complete_ifStatement() { |
| 325 if (errors.isEmpty || node is! IfStatement) { | 406 if (errors.isEmpty || node is! IfStatement) { |
| 326 return false; | 407 return false; |
| 327 } | 408 } |
| 328 IfStatement ifNode = node; | 409 IfStatement ifNode = node; |
| 329 if (ifNode != null) { | 410 if (ifNode != null) { |
| 330 if (ifNode.elseKeyword != null) { | 411 if (ifNode.elseKeyword != null) { |
| 331 return false; | 412 return false; |
| 332 } | 413 } |
| 333 var stmt = new _DoIfWhileStructure( | 414 var stmt = new _KeywordConditionBlockStructure( |
| 334 ifNode.ifKeyword, | 415 ifNode.ifKeyword, |
| 335 ifNode.leftParenthesis, | 416 ifNode.leftParenthesis, |
| 336 ifNode.condition, | 417 ifNode.condition, |
| 337 ifNode.rightParenthesis, | 418 ifNode.rightParenthesis, |
| 338 ifNode.thenStatement); | 419 ifNode.thenStatement); |
| 339 return _complete_ifOrWhileStatement( | 420 return _complete_ifOrWhileStatement( |
| 340 stmt, DartStatementCompletion.COMPLETE_IF_STMT); | 421 stmt, DartStatementCompletion.COMPLETE_IF_STMT); |
| 341 } | 422 } |
| 342 return false; | 423 return false; |
| 343 } | 424 } |
| 344 | 425 |
| 345 SourceBuilder _complete_keywordCondition(_DoIfWhileStructure statement) { | 426 SourceBuilder _complete_keywordCondition( |
| 427 _KeywordConditionBlockStructure statement) { |
| 346 SourceBuilder sb; | 428 SourceBuilder sb; |
| 347 String text = _baseNodeText(node); | |
| 348 if (statement.leftParenthesis.lexeme.isEmpty) { | 429 if (statement.leftParenthesis.lexeme.isEmpty) { |
| 349 if (!statement.rightParenthesis.lexeme.isEmpty) { | 430 if (!statement.rightParenthesis.lexeme.isEmpty) { |
| 350 // Quite unlikely to see this so don't try to fix it. | 431 // Quite unlikely to see this so don't try to fix it. |
| 351 return null; | 432 return null; |
| 352 } | 433 } |
| 353 sb = _sourceBuilderAfterKeyword(statement.keyword); | 434 sb = _sourceBuilderAfterKeyword(statement.keyword); |
| 354 sb.append('('); | 435 sb.append('('); |
| 355 sb.setExitOffset(); | 436 sb.setExitOffset(); |
| 356 sb.append(')'); | 437 sb.append(')'); |
| 357 } else { | 438 } else { |
| (...skipping 29 matching lines...) Expand all Loading... |
| 387 if (error != null) { | 468 if (error != null) { |
| 388 int insertOffset = error.offset + error.length; | 469 int insertOffset = error.offset + error.length; |
| 389 _addInsertEdit(insertOffset, ';'); | 470 _addInsertEdit(insertOffset, ';'); |
| 390 int offset = _appendNewlinePlusIndent() + 1 /* ';' */; | 471 int offset = _appendNewlinePlusIndent() + 1 /* ';' */; |
| 391 _setCompletionAt(DartStatementCompletion.SIMPLE_SEMICOLON, offset); | 472 _setCompletionAt(DartStatementCompletion.SIMPLE_SEMICOLON, offset); |
| 392 return true; | 473 return true; |
| 393 } | 474 } |
| 394 return false; | 475 return false; |
| 395 } | 476 } |
| 396 | 477 |
| 478 bool _complete_switchStatement() { |
| 479 // TODO(messick) Implement _complete_switchStatement |
| 480 return false; |
| 481 } |
| 482 |
| 483 bool _complete_tryStatement() { |
| 484 // TODO(messick) Implement _complete_tryStatement |
| 485 return false; |
| 486 } |
| 487 |
| 397 bool _complete_whileStatement() { | 488 bool _complete_whileStatement() { |
| 398 if (errors.isEmpty || node is! WhileStatement) { | 489 if (errors.isEmpty || node is! WhileStatement) { |
| 399 return false; | 490 return false; |
| 400 } | 491 } |
| 401 WhileStatement whileNode = node; | 492 WhileStatement whileNode = node; |
| 402 if (whileNode != null) { | 493 if (whileNode != null) { |
| 403 var stmt = new _DoIfWhileStructure( | 494 var stmt = new _KeywordConditionBlockStructure( |
| 404 whileNode.whileKeyword, | 495 whileNode.whileKeyword, |
| 405 whileNode.leftParenthesis, | 496 whileNode.leftParenthesis, |
| 406 whileNode.condition, | 497 whileNode.condition, |
| 407 whileNode.rightParenthesis, | 498 whileNode.rightParenthesis, |
| 408 whileNode.body); | 499 whileNode.body); |
| 409 return _complete_ifOrWhileStatement( | 500 return _complete_ifOrWhileStatement( |
| 410 stmt, DartStatementCompletion.COMPLETE_WHILE_STMT); | 501 stmt, DartStatementCompletion.COMPLETE_WHILE_STMT); |
| 411 } | 502 } |
| 412 return false; | 503 return false; |
| 413 } | 504 } |
| (...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 502 sb = new SourceBuilder(file, keyword.offset + len); | 593 sb = new SourceBuilder(file, keyword.offset + len); |
| 503 sb.append(' '); | 594 sb.append(' '); |
| 504 } else { | 595 } else { |
| 505 sb = new SourceBuilder(file, keyword.offset + len + 1); | 596 sb = new SourceBuilder(file, keyword.offset + len + 1); |
| 506 } | 597 } |
| 507 return sb; | 598 return sb; |
| 508 } | 599 } |
| 509 } | 600 } |
| 510 | 601 |
| 511 // Encapsulate common structure of if-statement and while-statement. | 602 // Encapsulate common structure of if-statement and while-statement. |
| 512 class _DoIfWhileStructure { | 603 class _KeywordConditionBlockStructure { |
| 513 final Token keyword; | 604 final Token keyword; |
| 514 final Token leftParenthesis, rightParenthesis; | 605 final Token leftParenthesis, rightParenthesis; |
| 515 final Expression condition; | 606 final Expression condition; |
| 516 final Statement block; | 607 final Statement block; |
| 517 | 608 |
| 518 _DoIfWhileStructure(this.keyword, this.leftParenthesis, this.condition, | 609 _KeywordConditionBlockStructure(this.keyword, this.leftParenthesis, |
| 519 this.rightParenthesis, this.block); | 610 this.condition, this.rightParenthesis, this.block); |
| 520 | 611 |
| 521 int get offset => keyword.offset; | 612 int get offset => keyword.offset; |
| 522 } | 613 } |
| OLD | NEW |