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 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_DO_STMT = const StatementCompletionKind( | |
37 'COMPLETE_DO_STMT', "Complete do-statement"); | |
36 static const COMPLETE_IF_STMT = const StatementCompletionKind( | 38 static const COMPLETE_IF_STMT = const StatementCompletionKind( |
37 'COMPLETE_IF_STMT', "Complete if-statement"); | 39 'COMPLETE_IF_STMT', "Complete if-statement"); |
38 static const COMPLETE_WHILE_STMT = const StatementCompletionKind( | 40 static const COMPLETE_WHILE_STMT = const StatementCompletionKind( |
39 'COMPLETE_WHILE_STMT', "Complete while-statement"); | 41 'COMPLETE_WHILE_STMT', "Complete while-statement"); |
40 } | 42 } |
41 | 43 |
42 /** | 44 /** |
43 * A description of a statement completion. | 45 * A description of a statement completion. |
44 * | 46 * |
45 * Clients may not extend, implement or mix-in this class. | 47 * Clients may not extend, implement or mix-in this class. |
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
155 // this asynchronous method, it is not safe to use the unit. | 157 // this asynchronous method, it is not safe to use the unit. |
156 if (analysisContext.getModificationStamp(source) != fileStamp) { | 158 if (analysisContext.getModificationStamp(source) != fileStamp) { |
157 return NO_COMPLETION; | 159 return NO_COMPLETION; |
158 } | 160 } |
159 node = new NodeLocator(selectionOffset).searchWithin(unit); | 161 node = new NodeLocator(selectionOffset).searchWithin(unit); |
160 if (node == null) { | 162 if (node == null) { |
161 return NO_COMPLETION; | 163 return NO_COMPLETION; |
162 } | 164 } |
163 // TODO(messick): This needs to work for declarations. | 165 // TODO(messick): This needs to work for declarations. |
164 node = node.getAncestor((n) => n is Statement); | 166 node = node.getAncestor((n) => n is Statement); |
167 if (_isEmptyStatement(node)) { | |
168 node = node.parent; | |
169 } | |
165 for (engine.AnalysisError error in statementContext.errors) { | 170 for (engine.AnalysisError error in statementContext.errors) { |
166 if (error.offset >= node.offset && | 171 if (error.offset >= node.offset && |
167 error.offset <= node.offset + node.length) { | 172 error.offset <= node.offset + node.length) { |
168 if (error.errorCode is! HintCode) { | 173 if (error.errorCode is! HintCode) { |
169 errors.add(error); | 174 errors.add(error); |
170 } | 175 } |
171 } | 176 } |
172 } | 177 } |
173 | 178 |
174 // TODO(messick) Consider changing (some of) this to a visitor. | 179 // TODO(messick) Consider changing (some of) this to a visitor. |
175 if (_complete_ifStatement() || | 180 if (_complete_ifStatement() || |
181 _complete_doStatement() || | |
176 _complete_whileStatement() || | 182 _complete_whileStatement() || |
177 _complete_simpleSemicolon() || | 183 _complete_simpleSemicolon() || |
178 _complete_simpleEnter()) { | 184 _complete_simpleEnter()) { |
179 return completion; | 185 return completion; |
180 } | 186 } |
181 return NO_COMPLETION; | 187 return NO_COMPLETION; |
182 } | 188 } |
183 | 189 |
184 void _addIndentEdit(SourceRange range, String oldIndent, String newIndent) { | 190 void _addIndentEdit(SourceRange range, String oldIndent, String newIndent) { |
185 SourceEdit edit = utils.createIndentEdit(range, oldIndent, newIndent); | 191 SourceEdit edit = utils.createIndentEdit(range, oldIndent, newIndent); |
186 doSourceChange_addElementEdit(change, unitElement, edit); | 192 doSourceChange_addElementEdit(change, unitElement, edit); |
187 } | 193 } |
188 | 194 |
189 void _addInsertEdit(int offset, String text) { | 195 void _addInsertEdit(int offset, String text) { |
190 SourceEdit edit = new SourceEdit(offset, 0, text); | 196 SourceEdit edit = new SourceEdit(offset, 0, text); |
191 doSourceChange_addElementEdit(change, unitElement, edit); | 197 doSourceChange_addElementEdit(change, unitElement, edit); |
192 } | 198 } |
193 | 199 |
194 void _addReplaceEdit(SourceRange range, String text) { | 200 void _addReplaceEdit(SourceRange range, String text) { |
195 SourceEdit edit = new SourceEdit(range.offset, range.length, text); | 201 SourceEdit edit = new SourceEdit(range.offset, range.length, text); |
196 doSourceChange_addElementEdit(change, unitElement, edit); | 202 doSourceChange_addElementEdit(change, unitElement, edit); |
197 } | 203 } |
198 | 204 |
199 void _appendEmptyBraces(SourceBuilder sb, [bool needsExitMark = false]) { | 205 void _appendEmptyBraces(SourceBuilder sb, [bool needsExitMark = false]) { |
200 sb.append(' {'); | 206 sb.append('{'); |
201 sb.append(eol); | 207 sb.append(eol); |
202 String indent = utils.getLinePrefix(selectionOffset); | 208 String indent = utils.getLinePrefix(selectionOffset); |
203 sb.append(indent); | 209 sb.append(indent); |
204 sb.append(utils.getIndent(1)); | 210 sb.append(utils.getIndent(1)); |
205 if (needsExitMark) { | 211 if (needsExitMark && sb.exitOffset == null) { |
206 sb.setExitOffset(); | 212 sb.setExitOffset(); |
207 } | 213 } |
208 sb.append(eol); | 214 sb.append(eol); |
209 sb.append(indent); | 215 sb.append(indent); |
210 sb.append('}'); | 216 sb.append('}'); |
211 } | 217 } |
212 | 218 |
213 int _appendNewlinePlusIndent() { | 219 int _appendNewlinePlusIndent() { |
214 // Append a newline plus proper indent and another newline. | 220 // Append a newline plus proper indent and another newline. |
215 // Return the position before the second newline. | 221 // Return the position before the second newline. |
216 String indent = utils.getLinePrefix(selectionOffset); | 222 String indent = utils.getLinePrefix(selectionOffset); |
217 int loc = utils.getLineNext(selectionOffset); | 223 int loc = utils.getLineNext(selectionOffset); |
218 _addInsertEdit(loc, indent + eol); | 224 _addInsertEdit(loc, indent + eol); |
219 return loc + indent.length; | 225 return loc + indent.length; |
220 } | 226 } |
221 | 227 |
222 bool _complete_ifOrWhileStatement( | 228 String _baseNodeText(AstNode astNode) { |
223 _IfWhileStructure statement, StatementCompletionKind kind) { | 229 String text = utils.getNodeText(astNode); |
224 String text = utils.getNodeText(node); | |
225 if (text.endsWith(eol)) { | 230 if (text.endsWith(eol)) { |
226 text = text.substring(0, text.length - eol.length); | 231 text = text.substring(0, text.length - eol.length); |
227 } | 232 } |
228 SourceBuilder sb; | 233 return text; |
229 bool needsExit = false; | 234 } |
230 if (statement.leftParenthesis.lexeme.isEmpty) { | 235 |
231 if (!statement.rightParenthesis.lexeme.isEmpty) { | 236 bool _complete_doStatement() { |
232 // Quite unlikely to see this so don't try to fix it. | 237 if (errors.isEmpty || node is! DoStatement) { |
233 return false; | 238 return false; |
239 } | |
240 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); | |
248 bool hasWhileKeyword = statement.whileKeyword.lexeme == "while"; | |
249 int exitDelta = 0; | |
250 if (statement.body is EmptyStatement) { | |
251 String text = utils.getNodeText(statement.body); | |
252 int delta = 0; | |
253 if (text.startsWith(';')) { | |
254 delta = 1; | |
255 _addReplaceEdit(rangeStartLength(statement.body.offset, delta), ''); | |
256 if (hasWhileKeyword) { | |
257 text = utils.getNodeText(statement); | |
258 if (text.indexOf(new RegExp(r'do\s*;\s*while')) == 0) { | |
scheglov
2017/04/14 17:33:39
What if there is a comment in the text?
messick
2017/04/14 17:37:01
Then this change is not applied. That's why I used
| |
259 int end = text.indexOf('while'); | |
260 int start = text.indexOf(';') + 1; | |
261 delta += end - start - 1; | |
262 _addReplaceEdit( | |
263 rangeStartLength(start + statement.offset, end - start), ' '); | |
264 } | |
265 } | |
266 sb = new SourceBuilder(file, sb.offset + delta); | |
267 sb.append(' '); | |
234 } | 268 } |
235 int len = statement.keyword.length; | 269 _appendEmptyBraces( |
236 if (text.length == len || | 270 sb, !(hasWhileKeyword && _isEmptyExpression(statement.condition))); |
237 !text.substring(len, len + 1).contains(new RegExp(r'\s'))) { | 271 if (delta != 0) { |
238 sb = new SourceBuilder(file, statement.offset + len); | 272 exitDelta = sb.length - delta; |
239 sb.append(' '); | 273 } |
274 } else if (_isEmptyBlock(statement.body)) { | |
275 sb = new SourceBuilder(sb.file, statement.body.end); | |
276 } | |
277 SourceBuilder sb2; | |
278 if (hasWhileKeyword) { | |
279 sb2 = _complete_keywordCondition(stmt); | |
280 if (sb2.length == 0) { | |
281 // true if condition is '()' | |
282 if (exitPosition != null) { | |
283 if (statement.semicolon.lexeme.isEmpty) { | |
284 _insertBuilder(sb); | |
285 sb = new SourceBuilder(file, exitPosition.offset + 1); | |
286 sb.append(';'); | |
287 } | |
288 } | |
240 } else { | 289 } else { |
241 sb = new SourceBuilder(file, statement.offset + len + 1); | 290 if (sb.exitOffset == null && sb2?.exitOffset != null) { |
291 _insertBuilder(sb); | |
292 sb = sb2; | |
293 sb.append(';'); | |
294 } else { | |
295 sb.append(sb2.toString()); | |
296 } | |
242 } | 297 } |
243 sb.append('('); | 298 } else { |
299 sb.append(" while ("); | |
244 sb.setExitOffset(); | 300 sb.setExitOffset(); |
245 sb.append(')'); | 301 sb.append(");"); |
246 } else { | |
247 if (_isEmptyExpression(statement.condition)) { | |
248 exitPosition = _newPosition(statement.leftParenthesis.offset + 1); | |
249 sb = new SourceBuilder(file, statement.rightParenthesis.offset + 1); | |
250 } else { | |
251 sb = new SourceBuilder(file, statement.rightParenthesis.offset + 1); | |
252 needsExit = true; | |
253 } | |
254 } | 302 } |
303 _insertBuilder(sb); | |
304 if (exitDelta != 0) { | |
305 exitPosition = | |
306 new Position(exitPosition.file, exitPosition.offset + exitDelta); | |
307 } | |
308 _setCompletion(DartStatementCompletion.COMPLETE_DO_STMT); | |
309 return true; | |
310 } | |
311 | |
312 bool _complete_ifOrWhileStatement( | |
313 _DoIfWhileStructure statement, StatementCompletionKind kind) { | |
314 SourceBuilder sb = _complete_keywordCondition(statement); | |
255 if (statement.block is EmptyStatement) { | 315 if (statement.block is EmptyStatement) { |
256 _appendEmptyBraces(sb, needsExit); | 316 sb.append(' '); |
317 _appendEmptyBraces(sb, exitPosition == null); | |
257 } | 318 } |
258 _insertBuilder(sb); | 319 _insertBuilder(sb); |
259 _setCompletion(kind); | 320 _setCompletion(kind); |
260 return true; | 321 return true; |
261 } | 322 } |
262 | 323 |
263 bool _complete_ifStatement() { | 324 bool _complete_ifStatement() { |
264 if (errors.isEmpty || node is! IfStatement) { | 325 if (errors.isEmpty || node is! IfStatement) { |
265 return false; | 326 return false; |
266 } | 327 } |
267 IfStatement ifNode = node; | 328 IfStatement ifNode = node; |
268 if (ifNode != null) { | 329 if (ifNode != null) { |
269 if (ifNode.elseKeyword != null) { | 330 if (ifNode.elseKeyword != null) { |
270 return false; | 331 return false; |
271 } | 332 } |
272 var stmt = new _IfWhileStructure(ifNode.ifKeyword, ifNode.leftParenthesis, | 333 var stmt = new _DoIfWhileStructure( |
273 ifNode.condition, ifNode.rightParenthesis, ifNode.thenStatement); | 334 ifNode.ifKeyword, |
335 ifNode.leftParenthesis, | |
336 ifNode.condition, | |
337 ifNode.rightParenthesis, | |
338 ifNode.thenStatement); | |
274 return _complete_ifOrWhileStatement( | 339 return _complete_ifOrWhileStatement( |
275 stmt, DartStatementCompletion.COMPLETE_IF_STMT); | 340 stmt, DartStatementCompletion.COMPLETE_IF_STMT); |
276 } | 341 } |
277 return false; | 342 return false; |
278 } | 343 } |
279 | 344 |
345 SourceBuilder _complete_keywordCondition(_DoIfWhileStructure statement) { | |
346 SourceBuilder sb; | |
347 String text = _baseNodeText(node); | |
348 if (statement.leftParenthesis.lexeme.isEmpty) { | |
349 if (!statement.rightParenthesis.lexeme.isEmpty) { | |
350 // Quite unlikely to see this so don't try to fix it. | |
351 return null; | |
352 } | |
353 sb = _sourceBuilderAfterKeyword(statement.keyword); | |
354 sb.append('('); | |
355 sb.setExitOffset(); | |
356 sb.append(')'); | |
357 } else { | |
358 if (_isEmptyExpression(statement.condition)) { | |
359 exitPosition = _newPosition(statement.leftParenthesis.offset + 1); | |
360 sb = new SourceBuilder(file, statement.rightParenthesis.offset + 1); | |
361 } else { | |
362 sb = new SourceBuilder(file, statement.rightParenthesis.offset + 1); | |
363 } | |
364 } | |
365 return sb; | |
366 } | |
367 | |
280 bool _complete_simpleEnter() { | 368 bool _complete_simpleEnter() { |
281 int offset; | 369 int offset; |
282 if (!errors.isEmpty) { | 370 if (!errors.isEmpty) { |
283 offset = selectionOffset; | 371 offset = selectionOffset; |
284 } else { | 372 } else { |
285 String indent = utils.getLinePrefix(selectionOffset); | 373 String indent = utils.getLinePrefix(selectionOffset); |
286 int loc = utils.getLineNext(selectionOffset); | 374 int loc = utils.getLineNext(selectionOffset); |
287 _addInsertEdit(loc, indent + eol); | 375 _addInsertEdit(loc, indent + eol); |
288 offset = loc + indent.length + eol.length; | 376 offset = loc + indent.length + eol.length; |
289 } | 377 } |
(...skipping 15 matching lines...) Expand all Loading... | |
305 } | 393 } |
306 return false; | 394 return false; |
307 } | 395 } |
308 | 396 |
309 bool _complete_whileStatement() { | 397 bool _complete_whileStatement() { |
310 if (errors.isEmpty || node is! WhileStatement) { | 398 if (errors.isEmpty || node is! WhileStatement) { |
311 return false; | 399 return false; |
312 } | 400 } |
313 WhileStatement whileNode = node; | 401 WhileStatement whileNode = node; |
314 if (whileNode != null) { | 402 if (whileNode != null) { |
315 var stmt = new _IfWhileStructure( | 403 var stmt = new _DoIfWhileStructure( |
316 whileNode.whileKeyword, | 404 whileNode.whileKeyword, |
317 whileNode.leftParenthesis, | 405 whileNode.leftParenthesis, |
318 whileNode.condition, | 406 whileNode.condition, |
319 whileNode.rightParenthesis, | 407 whileNode.rightParenthesis, |
320 whileNode.body); | 408 whileNode.body); |
321 return _complete_ifOrWhileStatement( | 409 return _complete_ifOrWhileStatement( |
322 stmt, DartStatementCompletion.COMPLETE_WHILE_STMT); | 410 stmt, DartStatementCompletion.COMPLETE_WHILE_STMT); |
323 } | 411 } |
324 return false; | 412 return false; |
325 } | 413 } |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
363 }); | 451 }); |
364 // add exit position | 452 // add exit position |
365 { | 453 { |
366 int exitOffset = builder.exitOffset; | 454 int exitOffset = builder.exitOffset; |
367 if (exitOffset != null) { | 455 if (exitOffset != null) { |
368 exitPosition = _newPosition(exitOffset); | 456 exitPosition = _newPosition(exitOffset); |
369 } | 457 } |
370 } | 458 } |
371 } | 459 } |
372 | 460 |
461 bool _isEmptyBlock(AstNode stmt) { | |
462 return stmt is Block && stmt.statements.isEmpty; | |
463 } | |
464 | |
373 bool _isEmptyExpression(Expression expr) { | 465 bool _isEmptyExpression(Expression expr) { |
374 if (expr is! SimpleIdentifier) { | 466 if (expr is! SimpleIdentifier) { |
375 return false; | 467 return false; |
376 } | 468 } |
377 SimpleIdentifier id = expr as SimpleIdentifier; | 469 SimpleIdentifier id = expr as SimpleIdentifier; |
378 return id.length == 0; | 470 return id.length == 0; |
379 } | 471 } |
380 | 472 |
473 bool _isEmptyStatement(AstNode stmt) { | |
474 return stmt is EmptyStatement || _isEmptyBlock(stmt); | |
475 } | |
476 | |
381 Position _newPosition(int offset) { | 477 Position _newPosition(int offset) { |
382 return new Position(file, offset); | 478 return new Position(file, offset); |
383 } | 479 } |
384 | 480 |
385 void _setCompletion(StatementCompletionKind kind, [List args]) { | 481 void _setCompletion(StatementCompletionKind kind, [List args]) { |
386 assert(exitPosition != null); | 482 assert(exitPosition != null); |
387 change.selection = exitPosition; | 483 change.selection = exitPosition; |
388 change.message = formatList(kind.message, args); | 484 change.message = formatList(kind.message, args); |
389 linkedPositionGroups.values | 485 linkedPositionGroups.values |
390 .forEach((group) => change.addLinkedEditGroup(group)); | 486 .forEach((group) => change.addLinkedEditGroup(group)); |
391 completion = new StatementCompletion(kind, change); | 487 completion = new StatementCompletion(kind, change); |
392 } | 488 } |
393 | 489 |
394 void _setCompletionAt(StatementCompletionKind kind, int offset, [List args]) { | 490 void _setCompletionAt(StatementCompletionKind kind, int offset, [List args]) { |
395 exitPosition = _newPosition(offset); | 491 exitPosition = _newPosition(offset); |
396 _setCompletion(kind, args); | 492 _setCompletion(kind, args); |
397 } | 493 } |
494 | |
495 SourceBuilder _sourceBuilderAfterKeyword(Token keyword) { | |
496 SourceBuilder sb; | |
497 String text = _baseNodeText(node); | |
498 text = text.substring(keyword.offset - node.offset); | |
499 int len = keyword.length; | |
500 if (text.length == len || | |
501 !text.substring(len, len + 1).contains(new RegExp(r'\s'))) { | |
502 sb = new SourceBuilder(file, keyword.offset + len); | |
503 sb.append(' '); | |
504 } else { | |
505 sb = new SourceBuilder(file, keyword.offset + len + 1); | |
506 } | |
507 return sb; | |
508 } | |
398 } | 509 } |
399 | 510 |
400 // Encapsulate common structure of if-statement and while-statement. | 511 // Encapsulate common structure of if-statement and while-statement. |
401 class _IfWhileStructure { | 512 class _DoIfWhileStructure { |
402 final Token keyword; | 513 final Token keyword; |
403 final Token leftParenthesis, rightParenthesis; | 514 final Token leftParenthesis, rightParenthesis; |
404 final Expression condition; | 515 final Expression condition; |
405 final Statement block; | 516 final Statement block; |
406 | 517 |
407 _IfWhileStructure(this.keyword, this.leftParenthesis, this.condition, | 518 _DoIfWhileStructure(this.keyword, this.leftParenthesis, this.condition, |
408 this.rightParenthesis, this.block); | 519 this.rightParenthesis, this.block); |
409 | 520 |
410 int get offset => keyword.offset; | 521 int get offset => keyword.offset; |
411 } | 522 } |
OLD | NEW |