Index: pkg/analysis_server/lib/src/services/completion/statement/statement_completion.dart |
diff --git a/pkg/analysis_server/lib/src/services/completion/statement/statement_completion.dart b/pkg/analysis_server/lib/src/services/completion/statement/statement_completion.dart |
index f5b75d4bf3cca4ee2db91a4883b9519d59b467ef..13c35c1c2dc23374a7dd95d80cd49c2f6dad872e 100644 |
--- a/pkg/analysis_server/lib/src/services/completion/statement/statement_completion.dart |
+++ b/pkg/analysis_server/lib/src/services/completion/statement/statement_completion.dart |
@@ -33,6 +33,8 @@ class DartStatementCompletion { |
'SIMPLE_ENTER', "Insert a newline at the end of the current line"); |
static const SIMPLE_SEMICOLON = const StatementCompletionKind( |
'SIMPLE_SEMICOLON', "Add a semicolon and newline"); |
+ static const COMPLETE_DO_STMT = const StatementCompletionKind( |
+ 'COMPLETE_DO_STMT', "Complete do-statement"); |
static const COMPLETE_IF_STMT = const StatementCompletionKind( |
'COMPLETE_IF_STMT', "Complete if-statement"); |
static const COMPLETE_WHILE_STMT = const StatementCompletionKind( |
@@ -162,6 +164,9 @@ class StatementCompletionProcessor { |
} |
// TODO(messick): This needs to work for declarations. |
node = node.getAncestor((n) => n is Statement); |
+ if (_isEmptyStatement(node)) { |
+ node = node.parent; |
+ } |
for (engine.AnalysisError error in statementContext.errors) { |
if (error.offset >= node.offset && |
error.offset <= node.offset + node.length) { |
@@ -173,6 +178,7 @@ class StatementCompletionProcessor { |
// TODO(messick) Consider changing (some of) this to a visitor. |
if (_complete_ifStatement() || |
+ _complete_doStatement() || |
_complete_whileStatement() || |
_complete_simpleSemicolon() || |
_complete_simpleEnter()) { |
@@ -197,12 +203,12 @@ class StatementCompletionProcessor { |
} |
void _appendEmptyBraces(SourceBuilder sb, [bool needsExitMark = false]) { |
- sb.append(' {'); |
+ sb.append('{'); |
sb.append(eol); |
String indent = utils.getLinePrefix(selectionOffset); |
sb.append(indent); |
sb.append(utils.getIndent(1)); |
- if (needsExitMark) { |
+ if (needsExitMark && sb.exitOffset == null) { |
sb.setExitOffset(); |
} |
sb.append(eol); |
@@ -219,41 +225,96 @@ class StatementCompletionProcessor { |
return loc + indent.length; |
} |
- bool _complete_ifOrWhileStatement( |
- _IfWhileStructure statement, StatementCompletionKind kind) { |
- String text = utils.getNodeText(node); |
+ String _baseNodeText(AstNode astNode) { |
+ String text = utils.getNodeText(astNode); |
if (text.endsWith(eol)) { |
text = text.substring(0, text.length - eol.length); |
} |
- SourceBuilder sb; |
- bool needsExit = false; |
- if (statement.leftParenthesis.lexeme.isEmpty) { |
- if (!statement.rightParenthesis.lexeme.isEmpty) { |
- // Quite unlikely to see this so don't try to fix it. |
- return false; |
- } |
- int len = statement.keyword.length; |
- if (text.length == len || |
- !text.substring(len, len + 1).contains(new RegExp(r'\s'))) { |
- sb = new SourceBuilder(file, statement.offset + len); |
+ return text; |
+ } |
+ |
+ bool _complete_doStatement() { |
+ if (errors.isEmpty || node is! DoStatement) { |
+ return false; |
+ } |
+ DoStatement statement = node; |
+ var stmt = new _DoIfWhileStructure( |
+ statement.whileKeyword, |
+ statement.leftParenthesis, |
+ statement.condition, |
+ statement.rightParenthesis, |
+ null); |
+ SourceBuilder sb = _sourceBuilderAfterKeyword(statement.doKeyword); |
+ bool hasWhileKeyword = statement.whileKeyword.lexeme == "while"; |
+ int exitDelta = 0; |
+ if (statement.body is EmptyStatement) { |
+ String text = utils.getNodeText(statement.body); |
+ int delta = 0; |
+ if (text.startsWith(';')) { |
+ delta = 1; |
+ _addReplaceEdit(rangeStartLength(statement.body.offset, delta), ''); |
+ if (hasWhileKeyword) { |
+ text = utils.getNodeText(statement); |
+ 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
|
+ int end = text.indexOf('while'); |
+ int start = text.indexOf(';') + 1; |
+ delta += end - start - 1; |
+ _addReplaceEdit( |
+ rangeStartLength(start + statement.offset, end - start), ' '); |
+ } |
+ } |
+ sb = new SourceBuilder(file, sb.offset + delta); |
sb.append(' '); |
- } else { |
- sb = new SourceBuilder(file, statement.offset + len + 1); |
} |
- sb.append('('); |
- sb.setExitOffset(); |
- sb.append(')'); |
- } else { |
- if (_isEmptyExpression(statement.condition)) { |
- exitPosition = _newPosition(statement.leftParenthesis.offset + 1); |
- sb = new SourceBuilder(file, statement.rightParenthesis.offset + 1); |
+ _appendEmptyBraces( |
+ sb, !(hasWhileKeyword && _isEmptyExpression(statement.condition))); |
+ if (delta != 0) { |
+ exitDelta = sb.length - delta; |
+ } |
+ } else if (_isEmptyBlock(statement.body)) { |
+ sb = new SourceBuilder(sb.file, statement.body.end); |
+ } |
+ SourceBuilder sb2; |
+ if (hasWhileKeyword) { |
+ sb2 = _complete_keywordCondition(stmt); |
+ if (sb2.length == 0) { |
+ // true if condition is '()' |
+ if (exitPosition != null) { |
+ if (statement.semicolon.lexeme.isEmpty) { |
+ _insertBuilder(sb); |
+ sb = new SourceBuilder(file, exitPosition.offset + 1); |
+ sb.append(';'); |
+ } |
+ } |
} else { |
- sb = new SourceBuilder(file, statement.rightParenthesis.offset + 1); |
- needsExit = true; |
+ if (sb.exitOffset == null && sb2?.exitOffset != null) { |
+ _insertBuilder(sb); |
+ sb = sb2; |
+ sb.append(';'); |
+ } else { |
+ sb.append(sb2.toString()); |
+ } |
} |
+ } else { |
+ sb.append(" while ("); |
+ sb.setExitOffset(); |
+ sb.append(");"); |
} |
+ _insertBuilder(sb); |
+ if (exitDelta != 0) { |
+ exitPosition = |
+ new Position(exitPosition.file, exitPosition.offset + exitDelta); |
+ } |
+ _setCompletion(DartStatementCompletion.COMPLETE_DO_STMT); |
+ return true; |
+ } |
+ |
+ bool _complete_ifOrWhileStatement( |
+ _DoIfWhileStructure statement, StatementCompletionKind kind) { |
+ SourceBuilder sb = _complete_keywordCondition(statement); |
if (statement.block is EmptyStatement) { |
- _appendEmptyBraces(sb, needsExit); |
+ sb.append(' '); |
+ _appendEmptyBraces(sb, exitPosition == null); |
} |
_insertBuilder(sb); |
_setCompletion(kind); |
@@ -269,14 +330,41 @@ class StatementCompletionProcessor { |
if (ifNode.elseKeyword != null) { |
return false; |
} |
- var stmt = new _IfWhileStructure(ifNode.ifKeyword, ifNode.leftParenthesis, |
- ifNode.condition, ifNode.rightParenthesis, ifNode.thenStatement); |
+ var stmt = new _DoIfWhileStructure( |
+ ifNode.ifKeyword, |
+ ifNode.leftParenthesis, |
+ ifNode.condition, |
+ ifNode.rightParenthesis, |
+ ifNode.thenStatement); |
return _complete_ifOrWhileStatement( |
stmt, DartStatementCompletion.COMPLETE_IF_STMT); |
} |
return false; |
} |
+ SourceBuilder _complete_keywordCondition(_DoIfWhileStructure statement) { |
+ SourceBuilder sb; |
+ String text = _baseNodeText(node); |
+ if (statement.leftParenthesis.lexeme.isEmpty) { |
+ if (!statement.rightParenthesis.lexeme.isEmpty) { |
+ // Quite unlikely to see this so don't try to fix it. |
+ return null; |
+ } |
+ sb = _sourceBuilderAfterKeyword(statement.keyword); |
+ sb.append('('); |
+ sb.setExitOffset(); |
+ sb.append(')'); |
+ } else { |
+ if (_isEmptyExpression(statement.condition)) { |
+ exitPosition = _newPosition(statement.leftParenthesis.offset + 1); |
+ sb = new SourceBuilder(file, statement.rightParenthesis.offset + 1); |
+ } else { |
+ sb = new SourceBuilder(file, statement.rightParenthesis.offset + 1); |
+ } |
+ } |
+ return sb; |
+ } |
+ |
bool _complete_simpleEnter() { |
int offset; |
if (!errors.isEmpty) { |
@@ -312,7 +400,7 @@ class StatementCompletionProcessor { |
} |
WhileStatement whileNode = node; |
if (whileNode != null) { |
- var stmt = new _IfWhileStructure( |
+ var stmt = new _DoIfWhileStructure( |
whileNode.whileKeyword, |
whileNode.leftParenthesis, |
whileNode.condition, |
@@ -370,6 +458,10 @@ class StatementCompletionProcessor { |
} |
} |
+ bool _isEmptyBlock(AstNode stmt) { |
+ return stmt is Block && stmt.statements.isEmpty; |
+ } |
+ |
bool _isEmptyExpression(Expression expr) { |
if (expr is! SimpleIdentifier) { |
return false; |
@@ -378,6 +470,10 @@ class StatementCompletionProcessor { |
return id.length == 0; |
} |
+ bool _isEmptyStatement(AstNode stmt) { |
+ return stmt is EmptyStatement || _isEmptyBlock(stmt); |
+ } |
+ |
Position _newPosition(int offset) { |
return new Position(file, offset); |
} |
@@ -395,16 +491,31 @@ class StatementCompletionProcessor { |
exitPosition = _newPosition(offset); |
_setCompletion(kind, args); |
} |
+ |
+ SourceBuilder _sourceBuilderAfterKeyword(Token keyword) { |
+ SourceBuilder sb; |
+ String text = _baseNodeText(node); |
+ text = text.substring(keyword.offset - node.offset); |
+ int len = keyword.length; |
+ if (text.length == len || |
+ !text.substring(len, len + 1).contains(new RegExp(r'\s'))) { |
+ sb = new SourceBuilder(file, keyword.offset + len); |
+ sb.append(' '); |
+ } else { |
+ sb = new SourceBuilder(file, keyword.offset + len + 1); |
+ } |
+ return sb; |
+ } |
} |
// Encapsulate common structure of if-statement and while-statement. |
-class _IfWhileStructure { |
+class _DoIfWhileStructure { |
final Token keyword; |
final Token leftParenthesis, rightParenthesis; |
final Expression condition; |
final Statement block; |
- _IfWhileStructure(this.keyword, this.leftParenthesis, this.condition, |
+ _DoIfWhileStructure(this.keyword, this.leftParenthesis, this.condition, |
this.rightParenthesis, this.block); |
int get offset => keyword.offset; |