diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/GroovyBundle.properties b/plugins/groovy/src/org/jetbrains/plugins/groovy/GroovyBundle.properties index ea12ceb3ed2d..6dc13a15071b 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/GroovyBundle.properties +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/GroovyBundle.properties @@ -59,6 +59,7 @@ lcurly.expected='{' expected comma.expected=',' expected rcurly.expected='}' expected rparen.expected=')' expected +comma.or.rparen.expected=',' or ')' expected semi.expected=';' expected gt.expected='>' expected else.without.if='else' without 'if' diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GroovyCompletionContributor.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GroovyCompletionContributor.java index de8dc8f2affb..1bfbe35dfce8 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GroovyCompletionContributor.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GroovyCompletionContributor.java @@ -358,8 +358,9 @@ public class GroovyCompletionContributor extends CompletionContributor { private static boolean couldContainReference(PsiElement position) { return IN_CATCH_TYPE.accepts(position) || - AFTER_AT.accepts(position) || - GroovyCompletionUtil.isFirstElementAfterPossibleModifiersInVariableDeclaration(position, true); + AFTER_AT.accepts(position) || + GroovyCompletionUtil.isFirstElementAfterPossibleModifiersInVariableDeclaration(position, true) || + GroovyCompletionUtil.isTupleVarNameWithoutTypeDeclared(position); } public static boolean isClassNamePossible(PsiElement position) { @@ -734,6 +735,10 @@ public class GroovyCompletionContributor extends CompletionContributor { iterator.advance(); } + while (!iterator.atEnd() && WHITE_SPACES_OR_COMMENTS.contains(iterator.getTokenType())) { + iterator.advance(); + } + if (!iterator.atEnd() && iterator.getTokenType() == mLPAREN) { return true; } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GroovyCompletionData.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GroovyCompletionData.java index 28639a1a80cd..777603b11017 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GroovyCompletionData.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GroovyCompletionData.java @@ -106,7 +106,7 @@ public class GroovyCompletionData { for (String keyword : addExtendsImplements(position)) { result.addElement(keyword(keyword, TailType.HUMBLE_SPACE_BEFORE_WORD)); } - + addExtendsForTypeParams(position, result); registerControlCompletion(position, result); @@ -216,7 +216,7 @@ public class GroovyCompletionData { if (ext && impl) { return new String[]{PsiKeyword.EXTENDS, PsiKeyword.IMPLEMENTS}; } - + return new String[]{ext ? PsiKeyword.EXTENDS : PsiKeyword.IMPLEMENTS}; } @@ -541,6 +541,8 @@ public class GroovyCompletionData { } + if (isTupleVarNameWithoutTypeDeclared(context)) return true; + if (previous != null && GroovyTokenTypes.mAT.equals(previous.getNode().getElementType())) { return false; } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GroovyCompletionUtil.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GroovyCompletionUtil.java index 315c00aa3f18..f085488f00dc 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GroovyCompletionUtil.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/completion/GroovyCompletionUtil.java @@ -186,10 +186,11 @@ public class GroovyCompletionUtil { return ((GrParameter)parent).getTypeElementGroovy() == null; } - final PsiElement parent1 = parent.getParent(); - if (!(parent1 instanceof GrVariableDeclaration)) return false; + final PsiElement pparent = parent.getParent(); + if (!(pparent instanceof GrVariableDeclaration)) return false; + if (((GrVariableDeclaration)pparent).isTuple()) return false; - final GrVariableDeclaration variableDeclaration = (GrVariableDeclaration)parent1; + final GrVariableDeclaration variableDeclaration = (GrVariableDeclaration)pparent; if (variableDeclaration.getTypeElementGroovy() != null) return false; return variableDeclaration.getVariables()[0] == parent; @@ -232,7 +233,6 @@ public class GroovyCompletionUtil { context.getParent().getParent().getParent().getParent() instanceof GrTypeDefinitionBody && context.getTextRange().getStartOffset() == context.getParent().getParent().getParent().getParent().getTextRange().getStartOffset(); - } @@ -307,8 +307,8 @@ public class GroovyCompletionUtil { private static boolean getterMatches(PrefixMatcher matcher, PsiMethod element, String importedName) { return isSimplePropertyGetter(element) && - (matcher.prefixMatches(getGetterNameNonBoolean(importedName)) || - element.getReturnType() == PsiType.BOOLEAN && matcher.prefixMatches(getGetterNameBoolean(importedName))); + (matcher.prefixMatches(getGetterNameNonBoolean(importedName)) || + element.getReturnType() == PsiType.BOOLEAN && matcher.prefixMatches(getGetterNameBoolean(importedName))); } public static LookupElement createClassLookupItem(PsiClass psiClass) { @@ -316,7 +316,9 @@ public class GroovyCompletionUtil { return AllClassesGetter.createLookupItem(psiClass, new GroovyClassNameInsertHandler()); } - private static List generateLookupForImportedElement(GroovyResolveResult resolveResult, String importedName, boolean alias) { + private static List generateLookupForImportedElement(GroovyResolveResult resolveResult, + String importedName, + boolean alias) { final PsiElement element = resolveResult.getElement(); assert element != null; final PsiSubstitutor substitutor = resolveResult.getSubstitutor(); @@ -380,7 +382,10 @@ public class GroovyCompletionUtil { } - private static LookupElementBuilder setTypeText(PsiElement element, LookupElementBuilder builder, PsiSubstitutor substitutor, @Nullable PsiElement position) { + private static LookupElementBuilder setTypeText(PsiElement element, + LookupElementBuilder builder, + PsiSubstitutor substitutor, + @Nullable PsiElement position) { PsiType type = null; if (element instanceof GrVariable) { if (position != null && GroovyRefactoringUtil.isLocalVariable(element)) { @@ -599,4 +604,14 @@ public class GroovyCompletionUtil { static boolean isTypelessParameter(PsiElement context) { return (context.getParent() instanceof GrParameter && ((GrParameter)context.getParent()).getTypeElementGroovy() == null); } + + public static boolean isTupleVarNameWithoutTypeDeclared(PsiElement position) { + PsiElement parent = position.getParent(); + PsiElement pparent = parent.getParent(); + return parent instanceof GrVariable && + ((GrVariable)parent).getNameIdentifierGroovy() == position && + ((GrVariable)parent).getTypeElementGroovy() == null && + pparent instanceof GrVariableDeclaration && + ((GrVariableDeclaration)pparent).isTuple(); + } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/parser/parsing/statements/TupleParse.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/parser/parsing/statements/TupleParse.java index 3ff32240595d..767795432bba 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/parser/parsing/statements/TupleParse.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/parser/parsing/statements/TupleParse.java @@ -17,60 +17,80 @@ package org.jetbrains.plugins.groovy.lang.parser.parsing.statements; import com.intellij.lang.PsiBuilder; import com.intellij.psi.tree.IElementType; -import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.GroovyBundle; import org.jetbrains.plugins.groovy.lang.parser.parsing.types.TypeSpec; import org.jetbrains.plugins.groovy.lang.parser.parsing.util.ParserUtils; -import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.*; +import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mCOMMA; +import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mIDENT; +import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mLPAREN; +import static org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes.*; /** * @author ilyas */ public class TupleParse { - public static boolean parseTuple(PsiBuilder builder, @Nullable IElementType tupleType, IElementType componentType) { - if (builder.getTokenType() != mLPAREN) return false; + public static boolean parseTupleForAssignment(PsiBuilder builder) { + PsiBuilder.Marker marker = parseTuple(builder, REFERENCE_EXPRESSION, false); + if (marker == null) return false; + marker.done(TUPLE_EXPRESSION); + return true; + } + + public static boolean parseTupleForVariableDeclaration(PsiBuilder builder) { + PsiBuilder.Marker marker = parseTuple(builder, VARIABLE, true); + if (marker == null) return false; + marker.drop(); + return true; + } + + public static PsiBuilder.Marker parseTuple(PsiBuilder builder, IElementType componentType, boolean acceptType) { + if (builder.getTokenType() != mLPAREN) return null; final PsiBuilder.Marker marker = builder.mark(); builder.advanceLexer(); + int count = 0; do { //skip unnecessary commas while (ParserUtils.getToken(builder, mCOMMA)) { + count++; builder.error(GroovyBundle.message("identifier.expected")); } - //parse modifiers for definitions - PsiBuilder.Marker typeMarker = builder.mark(); - TypeSpec.parse(builder); - if (builder.getTokenType() != mIDENT) { - typeMarker.rollbackTo(); + if (acceptType) { + //parse modifiers for definitions + PsiBuilder.Marker typeMarker = builder.mark(); + TypeSpec.parse(builder); + if (builder.getTokenType() != mIDENT) { + typeMarker.rollbackTo(); + } + else { + typeMarker.drop(); + } } - else { - typeMarker.drop(); - } - PsiBuilder.Marker varMarker = builder.mark(); + + PsiBuilder.Marker componentMarker = builder.mark(); if (!ParserUtils.getToken(builder, mIDENT)) { builder.error(GroovyBundle.message("identifier.expected")); - varMarker.drop(); + componentMarker.drop(); } else { - varMarker.done(componentType); + componentMarker.done(componentType); + count++; } } while (ParserUtils.getToken(builder, mCOMMA)); if (ParserUtils.getToken(builder, mRPAREN)) { - if (tupleType != null) { - marker.done(tupleType); - } - else { - marker.drop(); - } - return true; + return marker; + } + else if (count > 0) { //accept tuple if there was at least one comma or parsed tuple element inside it + builder.error(GroovyBundle.message("comma.or.rparen.expected")); + return marker; } else { marker.rollbackTo(); - return false; + return null; } } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/parser/parsing/statements/declaration/VariableDefinitions.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/parser/parsing/statements/declaration/VariableDefinitions.java index b00eabc7516a..609a3861ad07 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/parser/parsing/statements/declaration/VariableDefinitions.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/parser/parsing/statements/declaration/VariableDefinitions.java @@ -203,16 +203,11 @@ public class VariableDefinitions implements GroovyElementTypes { } private static IElementType parseDeclarator(PsiBuilder builder, boolean isTuple) { - if (!isTuple) { - if (builder.getTokenType() == mIDENT) { - ParserUtils.getToken(builder, mIDENT); - return mIDENT; - } + if (isTuple && builder.getTokenType() == mLPAREN && TupleParse.parseTupleForVariableDeclaration(builder)) { + return TUPLE_DECLARATION; } - else if (builder.getTokenType() == mLPAREN && isTuple) { - if (TupleParse.parseTuple(builder, null, VARIABLE)) { - return TUPLE_DECLARATION; - } + if (!isTuple && ParserUtils.getToken(builder, mIDENT)) { + return mIDENT; } return WRONGWAY; } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/parser/parsing/statements/expressions/AssignmentExpression.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/parser/parsing/statements/expressions/AssignmentExpression.java index 479ad8ba3b28..b986090b891c 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/parser/parsing/statements/expressions/AssignmentExpression.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/parser/parsing/statements/expressions/AssignmentExpression.java @@ -59,7 +59,7 @@ public class AssignmentExpression implements GroovyElementTypes { private static boolean parseSide(PsiBuilder builder, GroovyParser parser, boolean tuple, boolean comExprAllowed) { if (tuple) { - return TupleParse.parseTuple(builder, TUPLE_EXPRESSION, REFERENCE_EXPRESSION); + return TupleParse.parseTupleForAssignment(builder); } if (comExprAllowed) { diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/completion/KeywordCompletionTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/completion/KeywordCompletionTest.groovy index 44df64b0cb6a..46137e30c004 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/completion/KeywordCompletionTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/completion/KeywordCompletionTest.groovy @@ -95,6 +95,7 @@ public class KeywordCompletionTest extends CompletionTestBase { void testAssertInClosure() { doTest() } void testAfterLabel() { doTest() } void testKeywordsInParentheses() { doTest() } + void testCompletionInTupleVar(){ doTest() } String basePath = TestUtils.testDataPath + 'groovy/oldCompletion/keyword' diff --git a/plugins/groovy/testdata/groovy/oldCompletion/keyword/completionInTupleVar.test b/plugins/groovy/testdata/groovy/oldCompletion/keyword/completionInTupleVar.test new file mode 100644 index 000000000000..17018d6029aa --- /dev/null +++ b/plugins/groovy/testdata/groovy/oldCompletion/keyword/completionInTupleVar.test @@ -0,0 +1,13 @@ +def (a, b) = [1, 2] +----- +boolean +byte +char +double +float +i +int +integer +long +short +void \ No newline at end of file