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_forEachStatement | |
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) { | |
scheglov
2017/04/18 21:52:09
It might be more straightforward to use forNode.le
messick
2017/04/18 22:16:24
Done.
| |
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 // emptyCondition | |
347 int end = text.indexOf(')'); | |
348 sb = new SourceBuilder(file, forNode.leftSeparator.offset); | |
349 // TODO(messick) Consider adding two semicolons here. | |
350 _addReplaceEdit(rangeStartLength(sb.offset, end), '; '); | |
351 delta = end - '; '.length; | |
352 } else { | |
353 // emptyInitializersEmptyCondition | |
354 exitPosition = _newPosition(forNode.rightParenthesis.offset); | |
355 sb = new SourceBuilder(file, forNode.rightParenthesis.offset); | |
356 } | |
357 } else { | |
358 // emptyUpdaters | |
359 exitPosition = _newPosition(forNode.rightSeparator.offset); | |
360 sb = new SourceBuilder(file, forNode.rightSeparator.offset); | |
361 _addReplaceEdit(rangeStartLength(sb.offset, 0), '; '); | |
362 delta = -'; '.length; | |
363 } | |
364 } else if (_isEmptyExpression(forNode.initialization)) { | |
365 // emptyInitializers | |
366 exitPosition = _newPosition(forNode.rightParenthesis.offset); | |
367 sb = new SourceBuilder(file, forNode.rightParenthesis.offset); | |
368 } else { | |
369 int start = forNode.condition.offset + forNode.condition.length; | |
370 String text = | |
371 utils.getNodeText(forNode).substring(start - forNode.offset); | |
372 if (text.startsWith(new RegExp(r'\s*\)'))) { | |
373 // missingLeftSeparator | |
374 int end = text.indexOf(')'); | |
375 sb = new SourceBuilder(file, start); | |
376 _addReplaceEdit(rangeStartLength(start, end), '; '); | |
377 delta = end - '; '.length; | |
378 exitPosition = new Position(file, start); | |
379 } else { | |
380 // Not possible; any comment following init is attached to init. | |
381 exitPosition = _newPosition(forNode.rightParenthesis.offset); | |
382 sb = new SourceBuilder(file, forNode.rightParenthesis.offset); | |
383 } | |
384 } | |
385 } | |
386 if (forNode.body is EmptyStatement) { | |
387 // keywordOnly | |
388 sb.append(' '); | |
389 _appendEmptyBraces(sb, exitPosition == null); | |
390 } | |
391 if (delta != 0 && exitPosition != null) { | |
392 // missingLeftSeparator | |
393 exitPosition = new Position(file, exitPosition.offset - delta); | |
394 } | |
395 _insertBuilder(sb); | |
396 _setCompletion(DartStatementCompletion.COMPLETE_FOR_STMT); | |
397 return true; | |
398 } | |
399 | |
312 bool _complete_ifOrWhileStatement( | 400 bool _complete_ifOrWhileStatement( |
313 _DoIfWhileStructure statement, StatementCompletionKind kind) { | 401 _KeywordConditionBlockStructure statement, StatementCompletionKind kind) { |
314 SourceBuilder sb = _complete_keywordCondition(statement); | 402 SourceBuilder sb = _complete_keywordCondition(statement); |
315 if (statement.block is EmptyStatement) { | 403 if (statement.block is EmptyStatement) { |
316 sb.append(' '); | 404 sb.append(' '); |
317 _appendEmptyBraces(sb, exitPosition == null); | 405 _appendEmptyBraces(sb, exitPosition == null); |
318 } | 406 } |
319 _insertBuilder(sb); | 407 _insertBuilder(sb); |
320 _setCompletion(kind); | 408 _setCompletion(kind); |
321 return true; | 409 return true; |
322 } | 410 } |
323 | 411 |
324 bool _complete_ifStatement() { | 412 bool _complete_ifStatement() { |
325 if (errors.isEmpty || node is! IfStatement) { | 413 if (errors.isEmpty || node is! IfStatement) { |
326 return false; | 414 return false; |
327 } | 415 } |
328 IfStatement ifNode = node; | 416 IfStatement ifNode = node; |
329 if (ifNode != null) { | 417 if (ifNode != null) { |
330 if (ifNode.elseKeyword != null) { | 418 if (ifNode.elseKeyword != null) { |
331 return false; | 419 return false; |
332 } | 420 } |
333 var stmt = new _DoIfWhileStructure( | 421 var stmt = new _KeywordConditionBlockStructure( |
334 ifNode.ifKeyword, | 422 ifNode.ifKeyword, |
335 ifNode.leftParenthesis, | 423 ifNode.leftParenthesis, |
336 ifNode.condition, | 424 ifNode.condition, |
337 ifNode.rightParenthesis, | 425 ifNode.rightParenthesis, |
338 ifNode.thenStatement); | 426 ifNode.thenStatement); |
339 return _complete_ifOrWhileStatement( | 427 return _complete_ifOrWhileStatement( |
340 stmt, DartStatementCompletion.COMPLETE_IF_STMT); | 428 stmt, DartStatementCompletion.COMPLETE_IF_STMT); |
341 } | 429 } |
342 return false; | 430 return false; |
343 } | 431 } |
344 | 432 |
345 SourceBuilder _complete_keywordCondition(_DoIfWhileStructure statement) { | 433 SourceBuilder _complete_keywordCondition( |
434 _KeywordConditionBlockStructure statement) { | |
346 SourceBuilder sb; | 435 SourceBuilder sb; |
347 String text = _baseNodeText(node); | |
348 if (statement.leftParenthesis.lexeme.isEmpty) { | 436 if (statement.leftParenthesis.lexeme.isEmpty) { |
349 if (!statement.rightParenthesis.lexeme.isEmpty) { | 437 if (!statement.rightParenthesis.lexeme.isEmpty) { |
350 // Quite unlikely to see this so don't try to fix it. | 438 // Quite unlikely to see this so don't try to fix it. |
351 return null; | 439 return null; |
352 } | 440 } |
353 sb = _sourceBuilderAfterKeyword(statement.keyword); | 441 sb = _sourceBuilderAfterKeyword(statement.keyword); |
354 sb.append('('); | 442 sb.append('('); |
355 sb.setExitOffset(); | 443 sb.setExitOffset(); |
356 sb.append(')'); | 444 sb.append(')'); |
357 } else { | 445 } else { |
(...skipping 29 matching lines...) Expand all Loading... | |
387 if (error != null) { | 475 if (error != null) { |
388 int insertOffset = error.offset + error.length; | 476 int insertOffset = error.offset + error.length; |
389 _addInsertEdit(insertOffset, ';'); | 477 _addInsertEdit(insertOffset, ';'); |
390 int offset = _appendNewlinePlusIndent() + 1 /* ';' */; | 478 int offset = _appendNewlinePlusIndent() + 1 /* ';' */; |
391 _setCompletionAt(DartStatementCompletion.SIMPLE_SEMICOLON, offset); | 479 _setCompletionAt(DartStatementCompletion.SIMPLE_SEMICOLON, offset); |
392 return true; | 480 return true; |
393 } | 481 } |
394 return false; | 482 return false; |
395 } | 483 } |
396 | 484 |
485 bool _complete_switchStatement() { | |
486 // TODO(messick) Implement _complete_switchStatement | |
487 return false; | |
488 } | |
489 | |
490 bool _complete_tryStatement() { | |
491 // TODO(messick) Implement _complete_tryStatement | |
492 return false; | |
493 } | |
494 | |
397 bool _complete_whileStatement() { | 495 bool _complete_whileStatement() { |
398 if (errors.isEmpty || node is! WhileStatement) { | 496 if (errors.isEmpty || node is! WhileStatement) { |
399 return false; | 497 return false; |
400 } | 498 } |
401 WhileStatement whileNode = node; | 499 WhileStatement whileNode = node; |
402 if (whileNode != null) { | 500 if (whileNode != null) { |
403 var stmt = new _DoIfWhileStructure( | 501 var stmt = new _KeywordConditionBlockStructure( |
404 whileNode.whileKeyword, | 502 whileNode.whileKeyword, |
405 whileNode.leftParenthesis, | 503 whileNode.leftParenthesis, |
406 whileNode.condition, | 504 whileNode.condition, |
407 whileNode.rightParenthesis, | 505 whileNode.rightParenthesis, |
408 whileNode.body); | 506 whileNode.body); |
409 return _complete_ifOrWhileStatement( | 507 return _complete_ifOrWhileStatement( |
410 stmt, DartStatementCompletion.COMPLETE_WHILE_STMT); | 508 stmt, DartStatementCompletion.COMPLETE_WHILE_STMT); |
411 } | 509 } |
412 return false; | 510 return false; |
413 } | 511 } |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
455 if (exitOffset != null) { | 553 if (exitOffset != null) { |
456 exitPosition = _newPosition(exitOffset); | 554 exitPosition = _newPosition(exitOffset); |
457 } | 555 } |
458 } | 556 } |
459 } | 557 } |
460 | 558 |
461 bool _isEmptyBlock(AstNode stmt) { | 559 bool _isEmptyBlock(AstNode stmt) { |
462 return stmt is Block && stmt.statements.isEmpty; | 560 return stmt is Block && stmt.statements.isEmpty; |
463 } | 561 } |
464 | 562 |
465 bool _isEmptyExpression(Expression expr) { | 563 bool _isEmptyExpression(Expression expr) { |
scheglov
2017/04/18 21:52:09
This also could be simplified:
return expr is Simp
messick
2017/04/18 22:16:24
Done.
| |
466 if (expr is! SimpleIdentifier) { | 564 if (expr is! SimpleIdentifier) { |
467 return false; | 565 return false; |
468 } | 566 } |
469 SimpleIdentifier id = expr as SimpleIdentifier; | 567 SimpleIdentifier id = expr as SimpleIdentifier; |
470 return id.length == 0; | 568 return id.length == 0; |
471 } | 569 } |
472 | 570 |
473 bool _isEmptyStatement(AstNode stmt) { | 571 bool _isEmptyStatement(AstNode stmt) { |
474 return stmt is EmptyStatement || _isEmptyBlock(stmt); | 572 return stmt is EmptyStatement || _isEmptyBlock(stmt); |
475 } | 573 } |
(...skipping 26 matching lines...) Expand all Loading... | |
502 sb = new SourceBuilder(file, keyword.offset + len); | 600 sb = new SourceBuilder(file, keyword.offset + len); |
503 sb.append(' '); | 601 sb.append(' '); |
504 } else { | 602 } else { |
505 sb = new SourceBuilder(file, keyword.offset + len + 1); | 603 sb = new SourceBuilder(file, keyword.offset + len + 1); |
506 } | 604 } |
507 return sb; | 605 return sb; |
508 } | 606 } |
509 } | 607 } |
510 | 608 |
511 // Encapsulate common structure of if-statement and while-statement. | 609 // Encapsulate common structure of if-statement and while-statement. |
512 class _DoIfWhileStructure { | 610 class _KeywordConditionBlockStructure { |
513 final Token keyword; | 611 final Token keyword; |
514 final Token leftParenthesis, rightParenthesis; | 612 final Token leftParenthesis, rightParenthesis; |
515 final Expression condition; | 613 final Expression condition; |
516 final Statement block; | 614 final Statement block; |
517 | 615 |
518 _DoIfWhileStructure(this.keyword, this.leftParenthesis, this.condition, | 616 _KeywordConditionBlockStructure(this.keyword, this.leftParenthesis, |
519 this.rightParenthesis, this.block); | 617 this.condition, this.rightParenthesis, this.block); |
520 | 618 |
521 int get offset => keyword.offset; | 619 int get offset => keyword.offset; |
522 } | 620 } |
OLD | NEW |