Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(522)

Side by Side Diff: pkg/analysis_server/lib/src/services/completion/statement/statement_completion.dart

Issue 2816943005: Add completion for do-stmt, refactor for reuse (Closed)
Patch Set: Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | pkg/analysis_server/test/services/completion/statement/statement_completion_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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 }
OLDNEW
« no previous file with comments | « no previous file | pkg/analysis_server/test/services/completion/statement/statement_completion_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698