diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionData.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionData.java index cbe4c37d3ca9..3460698ebda6 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionData.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionData.java @@ -526,9 +526,12 @@ public class JavaCompletionData extends JavaAwareCompletionData{ return; } - final PsiExpressionStatement expressionStatement = PsiTreeUtil.getParentOfType(position, PsiExpressionStatement.class); - if (expressionStatement != null && expressionStatement.getTextRange().getStartOffset() == position.getTextRange().getStartOffset()) { - if (!psiElement().withSuperParent(2, PsiSwitchStatement.class).accepts(expressionStatement)) { + PsiStatement statement = PsiTreeUtil.getParentOfType(position, PsiExpressionStatement.class); + if (statement == null) { + statement = PsiTreeUtil.getParentOfType(position, PsiDeclarationStatement.class); + } + if (statement != null && statement.getTextRange().getStartOffset() == position.getTextRange().getStartOffset()) { + if (!psiElement().withSuperParent(2, PsiSwitchStatement.class).accepts(statement)) { result.addElement(createKeyword(position, PsiKeyword.FINAL)); } } diff --git a/java/java-impl/src/com/intellij/codeInsight/generation/OverrideImplementUtil.java b/java/java-impl/src/com/intellij/codeInsight/generation/OverrideImplementUtil.java index 8ad10f9f9dc7..078751201768 100644 --- a/java/java-impl/src/com/intellij/codeInsight/generation/OverrideImplementUtil.java +++ b/java/java-impl/src/com/intellij/codeInsight/generation/OverrideImplementUtil.java @@ -103,11 +103,12 @@ public class OverrideImplementUtil { Map finals = new LinkedHashMap(); Map concretes = new LinkedHashMap(); + LOG.assertTrue(aClass.isValid()); Collection allMethodSigs = aClass.getVisibleSignatures(); PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(aClass.getProject()).getResolveHelper(); for (HierarchicalMethodSignature signature : allMethodSigs) { PsiMethod method = signature.getMethod(); - LOG.assertTrue(method.isValid()); + LOG.assertTrue(method.isValid(), aClass); if (method.hasModifierProperty(PsiModifier.STATIC) || !resolveHelper.isAccessible(method, aClass, aClass)) continue; PsiClass hisClass = method.getContainingClass(); diff --git a/java/java-impl/src/com/intellij/psi/impl/PsiSuperMethodImplUtil.java b/java/java-impl/src/com/intellij/psi/impl/PsiSuperMethodImplUtil.java index 81c76b2737c8..06909b936df0 100644 --- a/java/java-impl/src/com/intellij/psi/impl/PsiSuperMethodImplUtil.java +++ b/java/java-impl/src/com/intellij/psi/impl/PsiSuperMethodImplUtil.java @@ -15,6 +15,7 @@ */ package com.intellij.psi.impl; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Pair; import com.intellij.psi.*; import com.intellij.psi.impl.source.HierarchicalMethodSignatureImpl; @@ -32,6 +33,7 @@ import org.jetbrains.annotations.Nullable; import java.util.*; public class PsiSuperMethodImplUtil { + private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.PsiSuperMethodImplUtil"); private static final PsiCacheKey, PsiClass> SIGNATURES_KEY = PsiCacheKey .create("SIGNATURES_KEY", new NotNullFunction>() { @NotNull @@ -134,6 +136,7 @@ public class PsiSuperMethodImplUtil { }); for (PsiMethod method : aClass.getMethods()) { + LOG.assertTrue(method.isValid()); if (!includePrivates && method.hasModifierProperty(PsiModifier.PRIVATE)) continue; final MethodSignatureBackedByPsiMethod signature = MethodSignatureBackedByPsiMethod.create(method, substitutor, isInRawContext); HierarchicalMethodSignatureImpl newH = new HierarchicalMethodSignatureImpl(signature); @@ -145,6 +148,7 @@ public class PsiSuperMethodImplUtil { } list.add(method); + LOG.assertTrue(newH.getMethod().isValid()); result.put(signature, newH); map.put(signature, newH); } @@ -188,6 +192,7 @@ public class PsiSuperMethodImplUtil { HierarchicalMethodSignatureImpl hierarchicalMethodSignature = entry.getValue(); MethodSignature methodSignature = entry.getKey(); if (result.get(methodSignature) == null && PsiUtil.isAccessible(hierarchicalMethodSignature.getMethod(), aClass, aClass)) { + LOG.assertTrue(hierarchicalMethodSignature.getMethod().isValid()); result.put(methodSignature, hierarchicalMethodSignature); } } @@ -213,6 +218,7 @@ public class PsiSuperMethodImplUtil { } // just drop an invalid method declaration there - to highlight accordingly else if (!result.containsKey(signature)) { + LOG.assertTrue(hierarchicalMethodSignature.getMethod().isValid()); result.put(signature, hierarchicalMethodSignature); } } diff --git a/java/java-tests/testData/codeInsight/completion/normal/EnumWithoutConstants.java b/java/java-tests/testData/codeInsight/completion/normal/EnumWithoutConstants.java index 6cf0d47ebe80..ea1066eea019 100644 --- a/java/java-tests/testData/codeInsight/completion/normal/EnumWithoutConstants.java +++ b/java/java-tests/testData/codeInsight/completion/normal/EnumWithoutConstants.java @@ -1 +1 @@ -public enum Foo { private Fo } \ No newline at end of file +public enum Foooooo { private Fooo } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/normal/EnumWithoutConstants_after.java b/java/java-tests/testData/codeInsight/completion/normal/EnumWithoutConstants_after.java index 99cf939d827e..0aef69ab4fdd 100644 --- a/java/java-tests/testData/codeInsight/completion/normal/EnumWithoutConstants_after.java +++ b/java/java-tests/testData/codeInsight/completion/normal/EnumWithoutConstants_after.java @@ -1 +1 @@ -public enum Foo { private Foo } \ No newline at end of file +public enum Foooooo { private Foooooo } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/normal/FinalInForLoop2.java b/java/java-tests/testData/codeInsight/completion/normal/FinalInForLoop2.java new file mode 100644 index 000000000000..41aefd1fcbd0 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/FinalInForLoop2.java @@ -0,0 +1,9 @@ +import java.util.ArrayList; + +public class Foooo { + + { + for (fin Object o : new ArrayList()) + } + +} diff --git a/java/java-tests/testData/codeInsight/completion/normalSorting/HonorUppercaseLetters.java b/java/java-tests/testData/codeInsight/completion/normalSorting/HonorUppercaseLetters.java deleted file mode 100644 index b4dac6bdb4d2..000000000000 --- a/java/java-tests/testData/codeInsight/completion/normalSorting/HonorUppercaseLetters.java +++ /dev/null @@ -1,6 +0,0 @@ -public class Beda { - { - Object clsLoader; - cL - } -} diff --git a/java/java-tests/testData/codeInsight/completion/normalSorting/UppercaseMatters.java b/java/java-tests/testData/codeInsight/completion/normalSorting/UppercaseMatters.java deleted file mode 100644 index ab7af0549fa2..000000000000 --- a/java/java-tests/testData/codeInsight/completion/normalSorting/UppercaseMatters.java +++ /dev/null @@ -1,8 +0,0 @@ -class Foo { - { - Object classLoader; - Object classBeforeLoader; - cL - } - -} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/normalSorting/UppercaseMatters2.java b/java/java-tests/testData/codeInsight/completion/normalSorting/UppercaseMatters2.java deleted file mode 100644 index 19fc5f857151..000000000000 --- a/java/java-tests/testData/codeInsight/completion/normalSorting/UppercaseMatters2.java +++ /dev/null @@ -1,8 +0,0 @@ -class Foo { - { - Object classLoader; - Object classLoader2; - cL - } - -} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionOrderingTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionOrderingTest.java index 787b21cc96f6..9c45f1491166 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionOrderingTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionOrderingTest.java @@ -5,7 +5,6 @@ package com.intellij.codeInsight.completion; import com.intellij.JavaTestUtil; -import com.intellij.codeInsight.CodeInsightSettings; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.impl.LookupImpl; import com.intellij.psi.PsiClass; @@ -68,32 +67,6 @@ public class NormalCompletionOrderingTest extends CompletionSortingTestCase { checkPreferredItems(0, "getService", "getService", "class"); } - public void testUppercaseMatters() throws Throwable { - final int old = CodeInsightSettings.getInstance().COMPLETION_CASE_SENSITIVE; - try { - CodeInsightSettings.getInstance().COMPLETION_CASE_SENSITIVE = CodeInsightSettings.FIRST_LETTER; - checkPreferredItems(0, "classLoader", "class", "classBeforeLoader", "clone"); - } - finally { - CodeInsightSettings.getInstance().COMPLETION_CASE_SENSITIVE = old; - } - } - - public void testUppercaseMatters2() throws Throwable { - final int old = CodeInsightSettings.getInstance().COMPLETION_CASE_SENSITIVE; - try { - CodeInsightSettings.getInstance().COMPLETION_CASE_SENSITIVE = CodeInsightSettings.ALL; - checkPreferredItems(0, "classLoader", "classLoader2"); - } - finally { - CodeInsightSettings.getInstance().COMPLETION_CASE_SENSITIVE = old; - } - } - - public void testHonorUppercaseLetters() throws Throwable { - checkPreferredItems(0, "clsLoader", "clone", "class"); - } - public void testClassStaticMembersInVoidContext() throws Throwable { checkPreferredItems(0, "booleanMethod", "voidMethod", "AN_OBJECT", "BOOLEAN", "class"); } @@ -232,4 +205,4 @@ public class NormalCompletionOrderingTest extends CompletionSortingTestCase { checkPreferredItems(0, "Bar9", "Bar1", "Bar2", "Bar3", "Bar4"); } -} \ No newline at end of file +} diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy b/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy index 7570da766b38..4677a94efca2 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy +++ b/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy @@ -709,8 +709,12 @@ public class NormalCompletionTest extends LightFixtureCompletionTestCase { public void testFinalInForLoop() throws Throwable { configure() - checkResultByFile(getTestName(false) + ".java") - assertOrderedEquals myFixture.lookupElementStrings, 'final' + assertStringItems 'final' + } + + public void testFinalInForLoop2() throws Throwable { + configure() + assertStringItems 'final', 'finalize' } public void testPrimitiveTypesInForLoop() throws Throwable { doPrimitiveTypeTest() } diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/completion/VariablesCompletionTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/completion/VariablesCompletionTest.java index 6c4ebbe4c565..e094aef705b4 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/completion/VariablesCompletionTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/completion/VariablesCompletionTest.java @@ -142,11 +142,4 @@ public class VariablesCompletionTest extends CompletionTestCase { assertStringItems(LineTokenizer.tokenize(FileDocumentManager.getInstance().getDocument(result).getCharsSequence(), false)); } - - private void assertStringItems(String... strings) { - assertNotNull(myItems); - for (int i = 0; i < myItems.length; i++) { - assertEquals(strings[i], myItems[i].toString()); - } - } } diff --git a/java/testFramework/src/com/intellij/codeInsight/completion/CompletionTestCase.java b/java/testFramework/src/com/intellij/codeInsight/completion/CompletionTestCase.java index 5ce8f5f978da..d91987d1302b 100644 --- a/java/testFramework/src/com/intellij/codeInsight/completion/CompletionTestCase.java +++ b/java/testFramework/src/com/intellij/codeInsight/completion/CompletionTestCase.java @@ -7,9 +7,12 @@ import com.intellij.codeInsight.lookup.impl.LookupImpl; import com.intellij.psi.statistics.StatisticsManager; import com.intellij.psi.statistics.impl.StatisticsManagerImpl; import com.intellij.testFramework.PlatformTestCase; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.Nullable; import java.util.Arrays; +import java.util.List; /** * @author mike @@ -95,4 +98,15 @@ public abstract class CompletionTestCase extends DaemonAnalyzerTestCase { protected LookupImpl getActiveLookup() { return (LookupImpl)LookupManager.getActiveLookup(myEditor); } + + protected void assertStringItems(String... strings) { + assertNotNull(myItems); + List actual = ContainerUtil.map(myItems, new Function() { + @Override + public String fun(LookupElement element) { + return element.getLookupString(); + } + }); + assertOrderedEquals(actual, strings); + } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CamelHumpMatcher.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CamelHumpMatcher.java index ae7fd6f9295b..c3670d93f9bf 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CamelHumpMatcher.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CamelHumpMatcher.java @@ -49,10 +49,10 @@ public class CamelHumpMatcher extends PrefixMatcher { ourLastCompletionCaseSetting = currentSetting; } - myMatcher = obtainMatcher(myRelaxedMatching); + myMatcher = obtainMatcher(myRelaxedMatching, myCaseSensitive); } if (myMatcher.matches(name)) { - if (myRelaxedMatching && obtainMatcher(false).matches(name)) { + if (myRelaxedMatching && obtainMatcher(false, myCaseSensitive).matches(name)) { return false; } @@ -62,11 +62,11 @@ public class CamelHumpMatcher extends PrefixMatcher { } } - private NameUtil.Matcher obtainMatcher(final boolean relax) { - String key = relax + myPrefix; + private NameUtil.Matcher obtainMatcher(final boolean relax, final boolean caseSensitive) { + String key = relax + myPrefix + caseSensitive; NameUtil.Matcher pattern = ourPatternCache.get(key); if (pattern == null) { - pattern = createCamelHumpsMatcher(relax); + pattern = createCamelHumpsMatcher(relax, caseSensitive); ourPatternCache.put(key, pattern); } return pattern; @@ -86,12 +86,8 @@ public class CamelHumpMatcher extends PrefixMatcher { if (itemCaseInsensitive && StringUtil.startsWithIgnoreCase(name, myPrefix) || prefixMatches(name)) { return true; } - if (itemCaseInsensitive && - CodeInsightSettings.ALL != CodeInsightSettings.getInstance().COMPLETION_CASE_SENSITIVE && - name.length() > 0) { - final char c = name.charAt(0); - String swappedCase = (Character.isUpperCase(c) ? Character.toLowerCase(c) : Character.toUpperCase(c)) + name.substring(1); - if (prefixMatches(swappedCase)) { + if (itemCaseInsensitive && CodeInsightSettings.ALL != CodeInsightSettings.getInstance().COMPLETION_CASE_SENSITIVE) { + if (obtainMatcher(false, false).matches(name)) { return true; } } @@ -104,8 +100,8 @@ public class CamelHumpMatcher extends PrefixMatcher { return new CamelHumpMatcher(prefix, myCaseSensitive, myRelaxedMatching); } - private NameUtil.Matcher createCamelHumpsMatcher(final boolean relaxedMatching) { - if (!myCaseSensitive) { + private NameUtil.Matcher createCamelHumpsMatcher(final boolean relaxedMatching, final boolean caseSensitive) { + if (!caseSensitive) { return NameUtil.buildCompletionMatcher(myPrefix, 0, true, true); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java index 50b649715a8a..7b92cdaa03e7 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java @@ -126,6 +126,7 @@ public class LookupImpl extends LightweightHint implements Lookup, Disposable { public LookupImpl(Project project, Editor editor, @NotNull LookupArranger arranger){ super(new JPanel(new BorderLayout())); setForceShowAsPopup(true); + setCancelOnClickOutside(false); myProject = project; myEditor = editor; diff --git a/platform/platform-resources-en/src/misc/registry.properties b/platform/platform-resources-en/src/misc/registry.properties index e2f11f731887..05be970b0c44 100644 --- a/platform/platform-resources-en/src/misc/registry.properties +++ b/platform/platform-resources-en/src/misc/registry.properties @@ -121,7 +121,7 @@ navbar.userActivityMergeTime=500 navbar.newpopup=true inspectionGadgets.telemetry.enabled=false -minuscule.humps.matching=false +minuscule.humps.matching=true minuscule.humps.matching.description=Camel Case without holding Shift in Ctrl+N/Ctrl+Shift+N etc jvmbugfix.mac.caccessibleLeak=true diff --git a/platform/util/src/com/intellij/psi/codeStyle/NameUtil.java b/platform/util/src/com/intellij/psi/codeStyle/NameUtil.java index 5d9283210452..21b842ddef2a 100644 --- a/platform/util/src/com/intellij/psi/codeStyle/NameUtil.java +++ b/platform/util/src/com/intellij/psi/codeStyle/NameUtil.java @@ -317,6 +317,10 @@ public class NameUtil { private enum WordState { NO_WORD, PREV_UC, WORD } + private static boolean isWordStart(char p) { + return Character.isUpperCase(p) || Character.isDigit(p); + } + private static void addAllWords(String word, List result) { CharacterIterator it = new StringCharacterIterator(word); StringBuffer b = new StringBuffer(); @@ -325,7 +329,7 @@ public class NameUtil { for (char c = it.first(); c != CharacterIterator.DONE; c = it.next()) { switch (state) { case NO_WORD: - if (!Character.isUpperCase(c)) { + if (!isWordStart(c)) { b.append(c); state = WordState.WORD; } @@ -335,7 +339,7 @@ public class NameUtil { } break; case PREV_UC: - if (!Character.isUpperCase(c)) { + if (!isWordStart(c)) { b = startNewWord(result, b, curPrevUC); b.append(c); state = WordState.WORD; @@ -347,7 +351,7 @@ public class NameUtil { } break; case WORD: - if (Character.isUpperCase(c)) { + if (isWordStart(c)) { startNewWord(result, b, c); b.setLength(0); state = WordState.PREV_UC; @@ -475,14 +479,13 @@ public class NameUtil { } } - public static class MinusculeMatcher implements Matcher { private final char[] myPattern; private final boolean myFirstLetterCaseMatters; public MinusculeMatcher(String pattern, boolean firstLetterCaseMatters) { myFirstLetterCaseMatters = firstLetterCaseMatters; - myPattern = pattern.replaceAll(":", "\\*:").replaceAll("\\.", "\\*\\.").toCharArray(); + myPattern = StringUtil.trimEnd(pattern, "* ").replaceAll(":", "\\*:").replaceAll("\\.", "\\*\\.").toCharArray(); } private boolean matches(int patternIndex, List words, int wordIndex) { @@ -501,7 +504,14 @@ public class NameUtil { if (isWordSeparator(w.charAt(0))) { assert w.length() == 1 : "'" + w + "'"; - return matches(isWordSeparator(myPattern[patternIndex]) ? patternIndex + 1 : patternIndex, words, wordIndex + 1); + if (isWordSeparator(myPattern[patternIndex])) { + return matches(patternIndex + 1, words, wordIndex + 1); + } + if (patternIndex == 0 && myFirstLetterCaseMatters) { + return false; + } + + return matches(patternIndex, words, wordIndex + 1); } if (patternIndex == 0 && myFirstLetterCaseMatters && w.charAt(0) != myPattern[0]) { @@ -512,7 +522,7 @@ public class NameUtil { return false; } - boolean uppers = Character.isUpperCase(myPattern[patternIndex]); + boolean uppers = isWordStart(myPattern[patternIndex]); int i = 1; while (true) { @@ -523,7 +533,7 @@ public class NameUtil { break; } char p = myPattern[patternIndex + i]; - if (uppers && Character.isUpperCase(p)) { + if (uppers && isWordStart(p)) { p = StringUtil.toLowerCase(p); } else { uppers = false; @@ -570,7 +580,7 @@ public class NameUtil { break; } List newWords = new ArrayList(); - newWords.add(s.substring(fromIndex)); + newWords.add(s.substring(next)); newWords.addAll(words.subList(i + 1, words.size())); if (matches(patternIndex, newWords, 0)) { return true; @@ -587,7 +597,7 @@ public class NameUtil { @Override public boolean matches(String name) { - StringTokenizer tokenizer = new StringTokenizer(name, " -_.:", true); + StringTokenizer tokenizer = new StringTokenizer(name, " -_.:/", true); List words = new ArrayList(); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/GroovyBundle.properties b/plugins/groovy/src/org/jetbrains/plugins/groovy/GroovyBundle.properties index 061901668483..3f1095fbe9e6 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/GroovyBundle.properties +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/GroovyBundle.properties @@ -167,6 +167,7 @@ import.what=Import ''{0}'' import.class=Import Class create.class.family.name=Create Class create.class.text=Create Class ''{0}'' +create.interface.text=Create Interface ''{0}'' dynamize.invalid.interface.text=Dynamize invalid interface instantiation dynamize.invalid.interface.family.name=Dynamize invalid code class.to.import.chooser.title=Choose class to import diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/intentions/CreateClassActionBase.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/intentions/CreateClassActionBase.java index efa93c88e7fb..8830fc1dce88 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/intentions/CreateClassActionBase.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/intentions/CreateClassActionBase.java @@ -33,6 +33,9 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.GroovyBundle; import org.jetbrains.plugins.groovy.actions.GroovyTemplatesFactory; import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrExtendsClause; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrImplementsClause; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrInterfaceDefinition; /** * @author ilyas @@ -47,7 +50,8 @@ public abstract class CreateClassActionBase implements IntentionAction { @NotNull public String getText() { - return GroovyBundle.message("create.class.text", myRefElement.getReferenceName()); + String referenceName = myRefElement.getReferenceName(); + return shouldCreateInterface() ? GroovyBundle.message("create.interface.text", referenceName) : GroovyBundle.message("create.class.text", referenceName); } @NotNull @@ -63,6 +67,11 @@ public abstract class CreateClassActionBase implements IntentionAction { return true; } + protected boolean shouldCreateInterface() { + PsiElement parent = myRefElement.getParent(); + return parent instanceof GrImplementsClause || parent instanceof GrExtendsClause && parent.getParent() instanceof GrInterfaceDefinition; + } + public static PsiClass createClassByType(final PsiDirectory directory, final String name, final PsiManager manager, diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/intentions/CreateClassFix.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/intentions/CreateClassFix.java index dde6cd0d67f8..f5771ff8c91f 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/intentions/CreateClassFix.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/intentions/CreateClassFix.java @@ -35,7 +35,6 @@ import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory; import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrNewExpression; -import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrImplementsClause; import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrMemberOwner; import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod; import org.jetbrains.plugins.groovy.lang.psi.expectedTypes.SupertypeConstraint; @@ -111,7 +110,7 @@ public abstract class CreateClassFix { PsiDirectory targetDirectory = getTargetDirectory(project, qualifier, name, module); if (targetDirectory == null) return; - String templateName = myRefElement.getParent() instanceof GrImplementsClause ? NewGroovyClassAction.GROOVY_INTERFACE : NewGroovyClassAction.GROOVY_CLASS; + String templateName = shouldCreateInterface() ? NewGroovyClassAction.GROOVY_INTERFACE : NewGroovyClassAction.GROOVY_CLASS; PsiClass targetClass = createClassByType(targetDirectory, name, manager, myRefElement, templateName); if (targetClass != null) { addImportForClass(groovyFile, qualifier, targetClass); diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/formatter/GroovyBlock.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/formatter/GroovyBlock.java index 61925b2ea77f..49ec0d0ead27 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/formatter/GroovyBlock.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/formatter/GroovyBlock.java @@ -47,7 +47,9 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrBinary import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrCommandArgumentList; import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameterList; +import java.util.Collections; import java.util.List; +import java.util.Map; /** * Block implementation for Groovy formatter @@ -61,15 +63,23 @@ public class GroovyBlock implements Block, GroovyElementTypes { final protected Indent myIndent; final protected Wrap myWrap; final protected CodeStyleSettings mySettings; + final protected Map myInnerAlignments; + protected List mySubBlocks = null; public GroovyBlock(@NotNull final ASTNode node, @Nullable final Alignment alignment, @NotNull final Indent indent, @Nullable final Wrap wrap, final CodeStyleSettings settings) { + this(node, alignment, indent, wrap, settings, Collections.emptyMap()); + } + + public GroovyBlock(@NotNull final ASTNode node, @Nullable final Alignment alignment, @NotNull final Indent indent, @Nullable final Wrap wrap, final CodeStyleSettings settings, + @NotNull Map innerAlignments) { myNode = node; myAlignment = alignment; myIndent = indent; myWrap = wrap; mySettings = settings; + myInnerAlignments = innerAlignments; } @NotNull diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/formatter/GroovyBlockGenerator.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/formatter/GroovyBlockGenerator.java index 5aee613507e2..ad740af12b3c 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/formatter/GroovyBlockGenerator.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/formatter/GroovyBlockGenerator.java @@ -27,6 +27,7 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.templateLanguages.OuterLanguageElement; import com.intellij.psi.tree.TokenSet; +import com.intellij.util.containers.CollectionFactory; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.groovy.GroovyFileType; import org.jetbrains.plugins.groovy.formatter.processors.GroovyIndentProcessor; @@ -34,6 +35,8 @@ import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes; import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes; import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase; import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrThrowsClause; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrLabeledStatement; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement; import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList; import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrCodeBlock; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrBinaryExpression; @@ -42,7 +45,9 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameterLi import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrExtendsClause; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; +import java.util.Map; /** * Utility class to generate myBlock hierarchy @@ -66,7 +71,7 @@ public class GroovyBlockGenerator implements GroovyElementTypes { PsiElement blockPsi = block.getNode().getPsi(); if (blockPsi instanceof GrBinaryExpression && !(blockPsi.getParent() instanceof GrBinaryExpression)) { - return generateForBinaryExpr(node, myWrap, mySettings); + return generateForBinaryExpr(node, myWrap, mySettings, block.myInnerAlignments); } //For multiline strings @@ -119,33 +124,93 @@ public class GroovyBlockGenerator implements GroovyElementTypes { // For Parameter lists if (isListLikeClause(blockPsi)) { final ArrayList subBlocks = new ArrayList(); - ASTNode[] children = node.getChildren(null); ASTNode prevChildNode = null; - final Alignment alignment = mustAlign(blockPsi, mySettings, children) ? Alignment.createAlignment() : null; + final Alignment alignment = mustAlign(blockPsi, mySettings, node.getChildren(null)) ? Alignment.createAlignment() : null; + for (ASTNode childNode : visibleChildren(node)) { + final Indent indent = GroovyIndentProcessor.getChildIndent(block, prevChildNode, childNode); + subBlocks.add(new GroovyBlock(childNode, isKeyword(childNode) ? null : alignment, indent, myWrap, mySettings)); + prevChildNode = childNode; + } + return subBlocks; + } + + if (blockPsi instanceof GrCodeBlock) { + List children = visibleChildren(node); + Map innerAlignments = calculateInnerAlignments(children); + final ArrayList subBlocks = new ArrayList(); + ASTNode prevChildNode = null; for (ASTNode childNode : children) { - if (canBeCorrectBlock(childNode)) { - final Indent indent = GroovyIndentProcessor.getChildIndent(block, prevChildNode, childNode); - subBlocks.add(new GroovyBlock(childNode, isKeyword(childNode) ? null : alignment, indent, myWrap, mySettings)); - prevChildNode = childNode; - } + final Indent indent = GroovyIndentProcessor.getChildIndent(block, prevChildNode, childNode); + subBlocks.add(new GroovyBlock(childNode, null, indent, myWrap, mySettings, innerAlignments)); + prevChildNode = childNode; } return subBlocks; } // For other cases final ArrayList subBlocks = new ArrayList(); - ASTNode[] children = getGroovyChildren(node); ASTNode prevChildNode = null; - for (ASTNode childNode : children) { - if (canBeCorrectBlock(childNode)) { - final Indent indent = GroovyIndentProcessor.getChildIndent(block, prevChildNode, childNode); - subBlocks.add(new GroovyBlock(childNode, blockPsi instanceof GrCodeBlock ? null : myAlignment, indent, myWrap, mySettings)); - prevChildNode = childNode; - } + for (ASTNode childNode : visibleChildren(node)) { + final Indent indent = GroovyIndentProcessor.getChildIndent(block, prevChildNode, childNode); + subBlocks.add(new GroovyBlock(childNode, myAlignment, indent, myWrap, mySettings, block.myInnerAlignments)); + prevChildNode = childNode; } return subBlocks; } + private static Map calculateInnerAlignments(List children) { + Map innerAlignments = CollectionFactory.hashMap(); + List currentGroup = null; + for (ASTNode child : children) { + PsiElement psi = child.getPsi(); + if (psi instanceof GrLabeledStatement) { + List table = getTable(((GrLabeledStatement)psi).getStatement()); + if (table.isEmpty()) { + currentGroup = null; + } + else { + currentGroup = new ArrayList(); + for (GrBinaryExpression expression : table) { + Alignment alignment = Alignment.createAlignment(true); + currentGroup.add(alignment); + innerAlignments.put(expression, alignment); + } + } + } else if (currentGroup != null && isTablePart(psi)) { + List table = getTable((GrStatement)psi); + for (int i = 0; i < Math.min(table.size(), currentGroup.size()); i++) { + innerAlignments.put(table.get(i), currentGroup.get(i)); + } + } else { + currentGroup = null; + } + } + return innerAlignments; + } + + private static List getTable(GrStatement statement) { + LinkedList result = new LinkedList(); + while (isTablePart(statement)) { + result.addFirst((GrBinaryExpression)statement); + statement = ((GrBinaryExpression)statement).getLeftOperand(); + } + return result; + } + + private static boolean isTablePart(PsiElement psi) { + return psi instanceof GrBinaryExpression && mBOR == ((GrBinaryExpression)psi).getOperationTokenType(); + } + + private static List visibleChildren(ASTNode node) { + ArrayList list = new ArrayList(); + for (ASTNode astNode : getGroovyChildren(node)) { + if (canBeCorrectBlock(astNode)) { + list.add(astNode); + } + } + return list; + } + private static boolean mustAlign(PsiElement blockPsi, CodeStyleSettings mySettings, ASTNode[] children) { // We don't want to align single call argument if it's a closure. The reason is that it looks better to have call like // @@ -276,23 +341,14 @@ public class GroovyBlockGenerator implements GroovyElementTypes { * @return * @param node */ - private static List generateForBinaryExpr(final ASTNode node, Wrap myWrap, CodeStyleSettings mySettings) { + private static List generateForBinaryExpr(final ASTNode node, Wrap myWrap, CodeStyleSettings mySettings, Map inner) { final ArrayList subBlocks = new ArrayList(); Alignment alignment = mySettings.ALIGN_MULTILINE_BINARY_OPERATION ? Alignment.createAlignment() : null; - GrBinaryExpression myExpr = (GrBinaryExpression) node.getPsi(); - ASTNode[] children = node.getChildren(null); - if (myExpr.getLeftOperand() instanceof GrBinaryExpression) { - addBinaryChildrenRecursively(myExpr.getLeftOperand(), subBlocks, Indent.getContinuationWithoutFirstIndent(), alignment, myWrap, mySettings); - } - for (ASTNode childNode : children) { - if (canBeCorrectBlock(childNode) && - !(childNode.getPsi() instanceof GrBinaryExpression)) { - subBlocks.add(new GroovyBlock(childNode, alignment, Indent.getContinuationWithoutFirstIndent(), myWrap, mySettings)); - } - } - if (myExpr.getRightOperand() instanceof GrBinaryExpression) { - addBinaryChildrenRecursively(myExpr.getRightOperand(), subBlocks, Indent.getContinuationWithoutFirstIndent(), alignment, myWrap, mySettings); - } + + + GrBinaryExpression binary = (GrBinaryExpression) node.getPsi(); + assert binary != null; + addBinaryChildrenRecursively(binary, subBlocks, Indent.getContinuationWithoutFirstIndent(), alignment, myWrap, mySettings, inner); return subBlocks; } @@ -307,23 +363,23 @@ public class GroovyBlockGenerator implements GroovyElementTypes { private static void addBinaryChildrenRecursively(PsiElement elem, List list, Indent indent, - Alignment alignment, Wrap myWrap, CodeStyleSettings mySettings) { + Alignment alignment, Wrap myWrap, CodeStyleSettings mySettings, Map inner) { if (elem == null) return; - ASTNode[] children = elem.getNode().getChildren(null); // For binary expressions if ((elem instanceof GrBinaryExpression)) { GrBinaryExpression myExpr = ((GrBinaryExpression) elem); if (myExpr.getLeftOperand() instanceof GrBinaryExpression) { - addBinaryChildrenRecursively(myExpr.getLeftOperand(), list, Indent.getContinuationWithoutFirstIndent(), alignment, myWrap, mySettings); + addBinaryChildrenRecursively(myExpr.getLeftOperand(), list, Indent.getContinuationWithoutFirstIndent(), alignment, myWrap, mySettings, inner); } - for (ASTNode childNode : children) { - if (canBeCorrectBlock(childNode) && - !(childNode.getPsi() instanceof GrBinaryExpression)) { - list.add(new GroovyBlock(childNode, alignment, indent, myWrap, mySettings)); + PsiElement op = ((GrBinaryExpression)elem).getOperationToken(); + for (ASTNode childNode : visibleChildren(elem.getNode())) { + PsiElement psi = childNode.getPsi(); + if (!(psi instanceof GrBinaryExpression)) { + list.add(new GroovyBlock(childNode, op == psi ? inner.get(myExpr) : alignment, indent, myWrap, mySettings)); } } if (myExpr.getRightOperand() instanceof GrBinaryExpression) { - addBinaryChildrenRecursively(myExpr.getRightOperand(), list, Indent.getContinuationWithoutFirstIndent(), alignment, myWrap, mySettings); + addBinaryChildrenRecursively(myExpr.getRightOperand(), list, Indent.getContinuationWithoutFirstIndent(), alignment, myWrap, mySettings, inner); } } } diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/formatter/FormatterTest.java b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/formatter/FormatterTest.java index b3b4005ab51d..7b02ce552336 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/formatter/FormatterTest.java +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/formatter/FormatterTest.java @@ -112,6 +112,8 @@ public class FormatterTest extends GroovyFormatterTestCase { public void testClosureAfterLineComment() throws Throwable { doTest(); } public void testAnnotationOnSeparateLine() throws Throwable { doTest(); } + public void testSpockTable() throws Throwable { doTest(); } + public void testElseIfs() throws Throwable { myTempSettings.SPECIAL_ELSE_IF_TREATMENT = false; doTest(); diff --git a/plugins/groovy/testdata/groovy/formatter/spockTable.test b/plugins/groovy/testdata/groovy/formatter/spockTable.test new file mode 100644 index 000000000000..2c898c194791 --- /dev/null +++ b/plugins/groovy/testdata/groovy/formatter/spockTable.test @@ -0,0 +1,31 @@ +class HelloSpock extends spock.lang.Specification { + def "length of Spock's and his friends' names"() { + expect: + name.size() == length + + where: + name | length | foo + "Spock" | 5 + "Kirk" | 4 | xxx | yyy + "Scotty" | 6 |dddddddddd | fff + + //aaa + a | b | c + } +} +----- +class HelloSpock extends spock.lang.Specification { + def "length of Spock's and his friends' names"() { + expect: + name.size() == length + + where: + name | length | foo + "Spock" | 5 + "Kirk" | 4 | xxx | yyy + "Scotty" | 6 | dddddddddd | fff + + //aaa + a | b | c + } +} \ No newline at end of file