Chromium Code Reviews| 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 |