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 |