diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java index 5138d2cabf91..7299220a847a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java @@ -195,7 +195,9 @@ public class HighlightMethodUtil { if (returnType.equals(substitutedSuperReturnType)) return null; if (!(returnType instanceof PsiPrimitiveType) && substitutedSuperReturnType.getDeepComponentType() instanceof PsiClassType) { - if (isJdk15 && TypeConversionUtil.isAssignable(substitutedSuperReturnType, returnType)) { + if (isJdk15 && LambdaUtil.performWithSubstitutedParameterBounds(methodSignature.getTypeParameters(), + methodSignature.getSubstitutor(), + () -> TypeConversionUtil.isAssignable(substitutedSuperReturnType, returnType))) { return null; } } diff --git a/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java b/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java index db8722c06f12..6332fd8ed0af 100644 --- a/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java +++ b/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java @@ -16,22 +16,35 @@ package com.intellij.codeInsight; import com.intellij.codeInspection.dataFlow.HardcodedContracts; +import com.intellij.codeInspection.dataFlow.Nullness; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.State; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.DefaultJDOMExternalizer; import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.WriteExternalException; -import com.intellij.psi.PsiElement; +import com.intellij.psi.*; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.util.CachedValueProvider; +import com.intellij.psi.util.CachedValuesManager; +import com.intellij.psi.util.PsiModificationTracker; +import com.intellij.util.containers.ContainerUtil; +import one.util.streamex.StreamEx; import org.jdom.Element; +import org.jetbrains.annotations.NotNull; import org.jetbrains.jps.model.serialization.java.compiler.JpsJavaCompilerNotNullableSerializer; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @State(name = "NullableNotNullManager") public class NullableNotNullManagerImpl extends NullableNotNullManager implements PersistentStateComponent { - public NullableNotNullManagerImpl() { + public static final String TYPE_QUALIFIER_NICKNAME = "javax.annotation.meta.TypeQualifierNickname"; + + public NullableNotNullManagerImpl(Project project) { + super(project); myNotNulls.addAll(getPredefinedNotNulls()); } @@ -80,4 +93,64 @@ public class NullableNotNullManagerImpl extends NullableNotNullManager implement LOG.error(e); } } + + private List getAllNullabilityNickNames() { + if (!getNotNulls().contains(JAVAX_ANNOTATION_NONNULL)) { + return Collections.emptyList(); + } + return CachedValuesManager.getManager(myProject).getCachedValue(myProject, () -> { + List result = new ArrayList<>(); + GlobalSearchScope scope = GlobalSearchScope.allScope(myProject); + for (PsiClass tqNick : JavaPsiFacade.getInstance(myProject).findClasses(TYPE_QUALIFIER_NICKNAME, scope)) { + result.addAll(ContainerUtil.findAll(MetaAnnotationUtil.getChildren(tqNick, scope), candidate -> { + String qname = candidate.getQualifiedName(); + if (qname == null || qname.startsWith("javax.annotation.")) return false; + return getNickNamedNullability(candidate) != Nullness.UNKNOWN; + })); + } + return CachedValueProvider.Result.create(result, PsiModificationTracker.MODIFICATION_COUNT); + }); + } + + private static Nullness getNickNamedNullability(@NotNull PsiClass psiClass) { + if (AnnotationUtil.findAnnotation(psiClass, TYPE_QUALIFIER_NICKNAME) == null) return Nullness.UNKNOWN; + + PsiAnnotation nonNull = AnnotationUtil.findAnnotation(psiClass, JAVAX_ANNOTATION_NONNULL); + return nonNull != null ? extractNullityFromWhenValue(nonNull) : Nullness.UNKNOWN; + } + + @NotNull + private static Nullness extractNullityFromWhenValue(PsiAnnotation nonNull) { + PsiAnnotationMemberValue when = nonNull.findAttributeValue("when"); + if (when instanceof PsiReferenceExpression) { + String refName = ((PsiReferenceExpression)when).getReferenceName(); + if ("ALWAYS".equals(refName)) { + return Nullness.NOT_NULL; + } + if ("MAYBE".equals(refName) || "NEVER".equals(refName)) { + return Nullness.NULLABLE; + } + } + return Nullness.UNKNOWN; + } + + private List filterNickNames(Nullness nullness) { + return StreamEx.of(getAllNullabilityNickNames()).filter(c -> getNickNamedNullability(c) == nullness).map(PsiClass::getQualifiedName).toList(); + } + + @NotNull + @Override + protected List getNullablesWithNickNames() { + return CachedValuesManager.getManager(myProject).getCachedValue(myProject, () -> + CachedValueProvider.Result.create(ContainerUtil.concat(getNullables(), filterNickNames(Nullness.NULLABLE)), + PsiModificationTracker.MODIFICATION_COUNT)); + } + + @NotNull + @Override + protected List getNotNullsWithNickNames() { + return CachedValuesManager.getManager(myProject).getCachedValue(myProject, () -> + CachedValueProvider.Result.create(ContainerUtil.concat(getNotNulls(), filterNickNames(Nullness.NOT_NULL)), + PsiModificationTracker.MODIFICATION_COUNT)); + } } diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaChainLookupElement.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaChainLookupElement.java index 7f3f8a33b0af..40a9d3b70af7 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaChainLookupElement.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaChainLookupElement.java @@ -19,7 +19,6 @@ import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementDecorator; import com.intellij.codeInsight.lookup.LookupElementPresentation; import com.intellij.codeInsight.lookup.TypedLookupItem; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.util.ClassConditionKey; import com.intellij.openapi.util.Key; @@ -39,19 +38,23 @@ import java.util.Set; */ public class JavaChainLookupElement extends LookupElementDecorator implements TypedLookupItem { public static final Key CHAIN_QUALIFIER = Key.create("CHAIN_QUALIFIER"); - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.JavaChainLookupElement"); public static final ClassConditionKey CLASS_CONDITION_KEY = ClassConditionKey.create(JavaChainLookupElement.class); private final LookupElement myQualifier; + private final String mySeparator; public JavaChainLookupElement(LookupElement qualifier, LookupElement main) { + this(qualifier, main, "."); + } + public JavaChainLookupElement(LookupElement qualifier, LookupElement main, String separator) { super(main); myQualifier = qualifier; + mySeparator = separator; } @NotNull @Override public String getLookupString() { - return maybeAddParentheses(myQualifier.getLookupString()) + "." + getDelegate().getLookupString(); + return maybeAddParentheses(myQualifier.getLookupString()) + mySeparator + getDelegate().getLookupString(); } public LookupElement getQualifier() { @@ -70,7 +73,7 @@ public class JavaChainLookupElement extends LookupElementDecorator= argumentOffset) { - return Arrays.asList(arguments).subList(argumentOffset, arguments.length); - } + return getRequiredMethodArguments(definitionCall, argumentOffset); } } return null; } + private static List getRequiredMethodArguments(@NotNull PsiMethodCallExpression definitionCall, int argumentOffset) { + final PsiExpression[] arguments = definitionCall.getArgumentList().getExpressions(); + + if (arguments.length == argumentOffset + 1) { + final PsiExpression[] arrayElements = getVarargAsArray(arguments[argumentOffset]); + if (arrayElements != null) { + return Arrays.asList(arrayElements); + } + } + if (arguments.length >= argumentOffset) { + return Arrays.asList(arguments).subList(argumentOffset, arguments.length); + } + return null; + } + + @Nullable + public static List getReflectionMethodParameterTypes(@NotNull PsiMethodCallExpression definitionCall, + int argumentOffset) { + List arguments = getRequiredMethodArguments(definitionCall, argumentOffset); + return arguments != null ? ContainerUtil.map(arguments, type -> getReflectiveType(type)) : null; + } + @Nullable static Arguments getActualMethodArguments(PsiExpression[] arguments, int argumentOffset, boolean allowVarargAsArray) { if (allowVarargAsArray && arguments.length == argumentOffset + 1) { diff --git a/java/java-impl/src/com/intellij/codeInspection/reflectiveAccess/JavaReflectionMemberAccessInspection.java b/java/java-impl/src/com/intellij/codeInspection/reflectiveAccess/JavaReflectionMemberAccessInspection.java index 951770eb6d56..90213c251cfa 100644 --- a/java/java-impl/src/com/intellij/codeInspection/reflectiveAccess/JavaReflectionMemberAccessInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/reflectiveAccess/JavaReflectionMemberAccessInspection.java @@ -279,6 +279,11 @@ public class JavaReflectionMemberAccessInspection extends BaseJavaBatchLocalInsp final List argumentTypes = ContainerUtil.map(methodArguments.expressions, JavaReflectionReferenceUtil::getReflectiveType); + return matchMethod(methods, argumentTypes); + } + + @Nullable + public static PsiMethod matchMethod(@NotNull PsiMethod[] methods, @NotNull List argumentTypes) { int mismatchCount = Integer.MAX_VALUE; PsiMethod bestGuess = null; for (PsiMethod method : methods) { diff --git a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangClassMemberReference.java b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangClassMemberReference.java index 67629f036bce..00ba95c77149 100644 --- a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangClassMemberReference.java +++ b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangClassMemberReference.java @@ -18,9 +18,11 @@ package com.intellij.psi.impl.source.resolve.reference.impl; import com.intellij.codeInsight.completion.InsertHandler; import com.intellij.codeInsight.completion.InsertionContext; import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.codeInspection.reflectiveAccess.JavaReflectionInvocationInspection; +import com.intellij.codeInspection.reflectiveAccess.JavaReflectionMemberAccessInspection; import com.intellij.psi.*; import com.intellij.psi.util.MethodSignatureBackedByPsiMethod; +import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.ContainerUtil; import gnu.trove.THashSet; @@ -28,10 +30,7 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Arrays; -import java.util.Comparator; -import java.util.Objects; -import java.util.Set; +import java.util.*; import static com.intellij.psi.impl.source.resolve.reference.impl.JavaReflectionReferenceUtil.*; @@ -73,17 +72,29 @@ public class JavaLangClassMemberReference extends PsiReferenceBase isRegularMethod(method) && isPublic(method)); - if (publicMethod != null) { - return publicMethod; + PsiMethod[] methods = psiClass.findMethodsByName(name, true); + if (methods.length > 1) { + methods = + ContainerUtil.filter(methods, method -> isRegularMethod(method) && isPublic(method)) + .toArray(PsiMethod.EMPTY_ARRAY); + if (methods.length > 1) { + return findOverloadedMethod(methods); + } } - return ContainerUtil.find(methods, method -> isRegularMethod(method)); + return methods.length != 0 ? methods[0] : null; } case GET_DECLARED_METHOD: { - final PsiMethod[] methods = psiClass.findMethodsByName(name, false); - return ContainerUtil.find(methods, method -> isRegularMethod(method) && isPotentiallyAccessible(method, psiClass)); + PsiMethod[] methods = psiClass.findMethodsByName(name, false); + if (methods.length > 1) { + methods = + ContainerUtil.filter(methods, method -> isRegularMethod(method) && isPotentiallyAccessible(method, psiClass)) + .toArray(PsiMethod.EMPTY_ARRAY); + if (methods.length > 1) { + return findOverloadedMethod(methods); + } + } + return methods.length != 0 ? methods[0] : null; } } } @@ -155,6 +166,18 @@ public class JavaLangClassMemberReference extends PsiReferenceBase parameterTypes = JavaReflectionInvocationInspection.getReflectionMethodParameterTypes(definitionCall, 1); + if (parameterTypes != null) { + return JavaReflectionMemberAccessInspection.matchMethod(methods, parameterTypes); + } + } + return null; + } + @Override public void handleInsert(InsertionContext context, LookupElement item) { final Object object = item.getObject(); diff --git a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangInvokeHandleReference.java b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangInvokeHandleReference.java index 838ee3abe01e..2574d768a80e 100644 --- a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangInvokeHandleReference.java +++ b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangInvokeHandleReference.java @@ -19,13 +19,16 @@ import com.intellij.codeInsight.completion.InsertHandler; import com.intellij.codeInsight.completion.InsertionContext; import com.intellij.codeInsight.completion.JavaLookupElementBuilder; import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInspection.reflectiveAccess.JavaLangInvokeHandleSignatureInspection; import com.intellij.openapi.util.Condition; import com.intellij.psi.*; import com.intellij.psi.util.MethodSignatureBackedByPsiMethod; +import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.ProcessingContext; import com.intellij.util.containers.ContainerUtil; +import com.siyeh.ig.psiutils.ParenthesesUtils; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -97,9 +100,25 @@ public class JavaLangInvokeHandleReference extends PsiReferenceBase filter) { - final PsiMethod[] methods = psiClass.findMethodsByName(name, true); - return ContainerUtil.find(methods, filter); + private PsiElement resolveMethod(@NotNull String name, @NotNull PsiClass psiClass, Condition filter) { + PsiMethod[] methods = psiClass.findMethodsByName(name, true); + if (methods.length != 0) { + methods = ContainerUtil.filter(methods, filter).toArray(PsiMethod.EMPTY_ARRAY); + if (methods.length > 1) { + final PsiMethodCallExpression definitionCall = PsiTreeUtil.getParentOfType(myElement, PsiMethodCallExpression.class); + if (definitionCall != null) { + final PsiExpression[] arguments = definitionCall.getArgumentList().getExpressions(); + if (arguments.length > 2) { + final PsiExpression typeExpression = ParenthesesUtils.stripParentheses(arguments[2]); + final ReflectiveSignature expectedSignature = JavaLangInvokeHandleSignatureInspection.composeMethodSignature(typeExpression); + if (expectedSignature != null) { + return ContainerUtil.find(methods, method -> expectedSignature.equals(getMethodSignature(method))); + } + } + } + } + } + return methods.length != 0 ? methods[0] : null; } @NotNull diff --git a/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java b/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java index b0ee0debbe61..a1ff985f7a1e 100644 --- a/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java +++ b/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java @@ -34,6 +34,7 @@ import java.util.*; */ public abstract class NullableNotNullManager { protected static final Logger LOG = Logger.getInstance(NullableNotNullManager.class); + protected final Project myProject; public String myDefaultNullable = AnnotationUtil.NULLABLE; public String myDefaultNotNull = AnnotationUtil.NOT_NULL; @@ -41,7 +42,7 @@ public abstract class NullableNotNullManager { public final JDOMExternalizableStringList myNotNulls = new JDOMExternalizableStringList(); private static final String JAVAX_ANNOTATION_NULLABLE = "javax.annotation.Nullable"; - private static final String JAVAX_ANNOTATION_NONNULL = "javax.annotation.Nonnull"; + protected static final String JAVAX_ANNOTATION_NONNULL = "javax.annotation.Nonnull"; static final String[] DEFAULT_NULLABLES = {AnnotationUtil.NULLABLE, JAVAX_ANNOTATION_NULLABLE, "javax.annotation.CheckForNull", @@ -49,7 +50,8 @@ public abstract class NullableNotNullManager { "edu.umd.cs.findbugs.annotations.Nullable", "android.support.annotation.Nullable" }; - public NullableNotNullManager() { + public NullableNotNullManager(Project project) { + myProject = project; Collections.addAll(myNullables, DEFAULT_NULLABLES); } @@ -202,7 +204,7 @@ public abstract class NullableNotNullManager { String qName = annotation.getQualifiedName(); if (qName == null) return null; - List contradictory = nullable ? getNotNulls() : getNullables(); + List contradictory = nullable ? getNotNullsWithNickNames() : getNullablesWithNickNames(); if (contradictory.contains(qName)) return null; return annotation; @@ -241,13 +243,24 @@ public abstract class NullableNotNullManager { } private PsiAnnotation findPlainNullabilityAnnotation(@NotNull PsiModifierListOwner owner, boolean checkBases) { - Set qNames = ContainerUtil.newHashSet(getNullables()); - qNames.addAll(getNotNulls()); + Set qNames = ContainerUtil.newHashSet(getNullablesWithNickNames()); + qNames.addAll(getNotNullsWithNickNames()); return checkBases && owner instanceof PsiMethod ? AnnotationUtil.findAnnotationInHierarchy(owner, qNames) : AnnotationUtil.findAnnotation(owner, qNames); } + + @NotNull + protected List getNullablesWithNickNames() { + return getNullables(); + } + + @NotNull + protected List getNotNullsWithNickNames() { + return getNotNulls(); + } + protected boolean hasHardcodedContracts(PsiElement element) { return false; } @@ -360,10 +373,10 @@ public abstract class NullableNotNullManager { public abstract List getPredefinedNotNulls(); public static boolean isNullableAnnotation(@NotNull PsiAnnotation annotation) { - return getInstance(annotation.getProject()).getNullables().contains(annotation.getQualifiedName()); + return getInstance(annotation.getProject()).getNullablesWithNickNames().contains(annotation.getQualifiedName()); } public static boolean isNotNullAnnotation(@NotNull PsiAnnotation annotation) { - return getInstance(annotation.getProject()).getNotNulls().contains(annotation.getQualifiedName()); + return getInstance(annotation.getProject()).getNotNullsWithNickNames().contains(annotation.getQualifiedName()); } } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/createSwitch/notAvailableInForUpdate.java b/java/java-tests/testData/codeInsight/createSwitch/notAvailableInForUpdate.java new file mode 100644 index 000000000000..f7d6fab86901 --- /dev/null +++ b/java/java-tests/testData/codeInsight/createSwitch/notAvailableInForUpdate.java @@ -0,0 +1,5 @@ +class Test { + void m(int[] array) { + for (int i = 1; i < array.length; i++) { } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/IncompatibleReturnTypeBounds.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/IncompatibleReturnTypeBounds.java new file mode 100644 index 000000000000..9e44426fec3b --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/IncompatibleReturnTypeBounds.java @@ -0,0 +1,10 @@ +class ListA { + public Ta foo() { throw new Error(); } +} + +class ListB extends ListA { + public Rb foo() { throw new Error(); } +} + +class ListC extends ListB { +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/completion/normal/ChainedMethodReference.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/completion/normal/ChainedMethodReference.java new file mode 100644 index 000000000000..03b94a1daa6b --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/completion/normal/ChainedMethodReference.java @@ -0,0 +1,5 @@ +class A { + { + Runnable r = Syst::setOu + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/completion/normal/ChainedMethodReference_after.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/completion/normal/ChainedMethodReference_after.java new file mode 100644 index 000000000000..eb1f75bd4b66 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/completion/normal/ChainedMethodReference_after.java @@ -0,0 +1,5 @@ +class A { + { + Runnable r = System::setOut; + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/assignFieldFromParameter/beforeAlreadyAssigned.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/assignFieldFromParameter/beforeAlreadyAssigned.java new file mode 100644 index 000000000000..94684be58489 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/assignFieldFromParameter/beforeAlreadyAssigned.java @@ -0,0 +1,10 @@ +// "Assign Parameter to Field 'myA'" "false" + +class Person { + int myA; + int myId; + void f(int a, String id) { + this.myA = foo(a); + } + int foo(int a) {return a;} +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/assignFieldFromParameter/beforeCheckAssignability.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/assignFieldFromParameter/beforeCheckAssignability.java new file mode 100644 index 000000000000..38db6f1a210d --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/assignFieldFromParameter/beforeCheckAssignability.java @@ -0,0 +1,10 @@ +// "Assign Parameter to Field 'myId'" "false" + +class Person { + int a; + int myId; + void f(int a, String id) { + this.a = foo(a); + } + int foo(int a) {return a;} +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithArray/beforeRejectNullType.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithArray/beforeRejectNullType.java new file mode 100644 index 000000000000..6972290707e4 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithArray/beforeRejectNullType.java @@ -0,0 +1,9 @@ +// "Surround with array initialization" "false" +class A { + void m1(String[] s, + String[] s2, + String[] s3) {} + { + m1( null, null); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/TypeQualifierNickname.java b/java/java-tests/testData/inspection/dataFlow/fixture/TypeQualifierNickname.java new file mode 100644 index 000000000000..c2f20a40f1a3 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/TypeQualifierNickname.java @@ -0,0 +1,15 @@ +import javax.annotation.meta.*; + +@TypeQualifierNickname() +@javax.annotation.Nonnull(when = When.MAYBE) +@interface NullableNick {} + +interface UnknownInterface { + void foo(String s); +} + +class ImplWithNotNull implements UnknownInterface { + public void foo(@NullableNick String s) { + System.out.println(s.hashCode()); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/Normal8CompletionTest.groovy b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/Normal8CompletionTest.groovy index 30a1fbd37628..ae0af6127049 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/Normal8CompletionTest.groovy +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/Normal8CompletionTest.groovy @@ -212,37 +212,37 @@ class Test88 { void testCollectorsToList() { configureByTestName() selectItem(myItems.find { it.lookupString.contains('toList') }) - checkResultByFile(getTestName(false) + "_after.java") + checkResultByFileName() } void testStaticallyImportedCollectorsToList() { configureByTestName() selectItem(myItems.find { it.lookupString.contains('collect(toList())') }) - checkResultByFile(getTestName(false) + "_after.java") + checkResultByFileName() } void testAllCollectors() { configureByTestName() myFixture.assertPreferredCompletionItems 0, 'collect', 'collect', 'collect(Collectors.toCollection())', 'collect(Collectors.toList())', 'collect(Collectors.toSet())' selectItem(myItems.find { it.lookupString.contains('toCollection') }) - checkResultByFile(getTestName(false) + "_after.java") + checkResultByFileName() } void testCollectorsToSet() { configureByTestName() selectItem(myItems.find { it.lookupString.contains('toSet') }) - checkResultByFile(getTestName(false) + "_after.java") + checkResultByFileName() } void testNoExplicitTypeArgsInTernary() { configureByTestName() selectItem(myItems.find { it.lookupString.contains('empty') }) - checkResultByFile(getTestName(false) + "_after.java") + checkResultByFileName() } void testCallBeforeLambda() { configureByTestName() - checkResultByFile(getTestName(false) + "_after.java") + checkResultByFileName() } void testLambdaInAmbiguousCall() { @@ -264,13 +264,13 @@ class Test88 { void testNoSemicolonAfterVoidMethodInLambda() { configureByTestName() myFixture.type('l\t') - checkResultByFile(getTestName(false) + "_after.java") + checkResultByFileName() } void testFinishMethodReferenceWithColon() { configureByTestName() myFixture.type(':') - checkResultByFile(getTestName(false) + "_after.java") + checkResultByFileName() } void testPreferLocalsOverMethodRefs() { @@ -286,14 +286,22 @@ class Test88 { "}") configureByTestName() myFixture.type('\n') - checkResultByFile(getTestName(false) + "_after.java") + checkResultByFileName() } void testOverrideMethodAsDefault() { configureByTestName() assert LookupElementPresentation.renderElement(myFixture.lookupElements[0]).itemText == 'default void run' myFixture.type('\t') - checkResultByFile(getTestName(false) + "_after.java") + checkResultByFileName() } + void testChainedMethodReference() { + configureByTestName() + checkResultByFileName() + } + + private checkResultByFileName() { + checkResultByFile(getTestName(false) + "_after.java") + } } \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/GenericsHighlightingTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/GenericsHighlightingTest.java index 2f0480f4bce6..d2276ff4bc6e 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/GenericsHighlightingTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/GenericsHighlightingTest.java @@ -180,6 +180,7 @@ public class GenericsHighlightingTest extends LightDaemonAnalyzerTestCase { public void testInaccessibleThroughWildcard() { doTest7Incompatibility(false);} public void testInconvertibleTypes() { doTest5(false); } public void testIncompatibleReturnType() { doTest5(false); } + public void testContinueInferenceAfterFirstRawResult() { doTest5(false); } public void testDoNotAcceptLowerBoundIfRaw() { doTest5(false); } public void testStaticOverride() { doTest5(false); } @@ -342,6 +343,7 @@ public class GenericsHighlightingTest extends LightDaemonAnalyzerTestCase { public void testIDEA71582() { doTest5(false); } public void testIDEA65377() { doTest5(false); } public void testIDEA113526() { doTest5(true); } + public void testIncompatibleReturnTypeBounds() { doTest(LanguageLevel.JDK_1_7, JavaSdkVersion.JDK_1_7, false); } public void testIDEA116493() { doTest(LanguageLevel.JDK_1_7, JavaSdkVersion.JDK_1_7, false); } public void testIDEA117827() { doTest(LanguageLevel.JDK_1_7, JavaSdkVersion.JDK_1_7, false); } public void testIDEA118037() { doTest(LanguageLevel.JDK_1_7, JavaSdkVersion.JDK_1_7, false); } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/intention/CreateSwitchTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/intention/CreateSwitchTest.java index 286d1b717a53..e342f4c8692e 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/intention/CreateSwitchTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/intention/CreateSwitchTest.java @@ -55,6 +55,10 @@ public class CreateSwitchTest extends JavaCodeInsightFixtureTestCase { doTestNotAvailable(); } + public void testNotAvailableInForUpdate() { + doTestNotAvailable(); + } + private void doTestString() { final LanguageLevelProjectExtension languageLevelProjectExtension = LanguageLevelProjectExtension.getInstance(getProject()); final LanguageLevel oldLanguageLevel = languageLevelProjectExtension.getLanguageLevel(); diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/JavaLangInvokeHandleNavigationTest.kt b/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/JavaLangInvokeHandleNavigationTest.kt index 73e7aa0e2f4c..b8f0b87796bb 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/JavaLangInvokeHandleNavigationTest.kt +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/JavaLangInvokeHandleNavigationTest.kt @@ -16,10 +16,13 @@ package com.intellij.java.codeInsight.navigation import com.intellij.psi.PsiMember +import com.intellij.psi.PsiMethod import com.intellij.psi.PsiReference import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase import junit.framework.TestCase +import org.intellij.lang.annotations.Language import org.intellij.lang.annotations.MagicConstant +import org.jetbrains.annotations.NonNls /** * @author Pavel.Dolgov @@ -74,6 +77,64 @@ class JavaLangInvokeHandleNavigationTest : LightCodeInsightFixtureTestCase() { fun testStaticSetter6() = doNegativeTest("pf1", STATIC_SETTER) fun testStaticSetter7() = doNegativeTest("m1", STATIC_SETTER) + fun testOverloadedBothPublic() = doTestOverloaded( + """public class Overloaded { + public void foo(int n) {} + public void foo(String s) {} +}""", VIRTUAL, "java.lang.String") + + fun testOverloadedFirstPublic() = doTestOverloaded( + """public class Overloaded { + public void foo(int n) {} + void foo(String s) {} +}""", VIRTUAL, "int") + + fun testOverloadedSecondPublic() = doTestOverloaded( + """public class Overloaded { + void foo(int n) {} + public void foo(String s) {} +}""", VIRTUAL, "java.lang.String") + + fun testOverloadedInherited() { + myFixture.addClass("""public class OverloadedParent { + public static void foo(String s) {} +}""") + + doTestOverloaded( + """public class Overloaded extends OverloadedParent { + public static void foo(int n) {} +}""", STATIC, "java.lang.String") + } + + fun testOverloadedStatic() = doTestOverloaded( + """public class Overloaded { + public static void foo(int n) {} + public static void foo(String s) {} +}""", STATIC, "java.lang.String") + + + private fun doTestOverloaded(@NonNls @Language("JAVA") classText: String, function: String, vararg expectedParameterTypes: String) { + myFixture.addClass(classText) + + val methodType = arrayOf("void", *expectedParameterTypes).map { "$it.class" }.joinToString(", ") + val member = doTestImpl("foo", """ +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +class Main { + void foo() throws ReflectiveOperationException { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.$function(Overloaded.class, "foo", MethodType.methodType($methodType)); + } +}""") + + TestCase.assertTrue("Is method", member is PsiMethod) + val parameters = (member as PsiMethod).parameterList.parameters + TestCase.assertEquals("Parameter count", expectedParameterTypes.size, parameters.size) + for (i in 0 until expectedParameterTypes.size) { + TestCase.assertEquals("Parameter $i", expectedParameterTypes[i], parameters[i].type.canonicalText) + } + } + private fun doTest(name: String, @MagicConstant(stringValues = arrayOf(VIRTUAL, STATIC, SPECIAL, @@ -83,7 +144,7 @@ class JavaLangInvokeHandleNavigationTest : LightCodeInsightFixtureTestCase() { doTestImpl(name, getMainClassText(name, function)) } - private fun doTestImpl(name: String, mainClassText: String) { + private fun doTestImpl(name: String, mainClassText: String): PsiMember { val reference = getReference(mainClassText) TestCase.assertEquals("Reference text", name, reference.canonicalText) val resolved = reference.resolve() @@ -91,6 +152,7 @@ class JavaLangInvokeHandleNavigationTest : LightCodeInsightFixtureTestCase() { TestCase.assertTrue("Target is a member", resolved is PsiMember) val member = resolved as PsiMember? TestCase.assertEquals("Target name", name, member!!.name) + return member } private fun doNegativeTest(name: String, diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/JavaReflectionNavigationTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/JavaReflectionNavigationTest.java index e904b9b9deb1..1102cb973937 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/JavaReflectionNavigationTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/navigation/JavaReflectionNavigationTest.java @@ -15,9 +15,7 @@ */ package com.intellij.java.codeInsight.navigation; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiMember; -import com.intellij.psi.PsiReference; +import com.intellij.psi.*; import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; import org.intellij.lang.annotations.Language; import org.intellij.lang.annotations.MagicConstant; @@ -93,6 +91,70 @@ public class JavaReflectionNavigationTest extends LightCodeInsightFixtureTestCas "}"); } + public void testOverloadedMethodBothPublic() { + doTestOverloadedMethod("foo", + "class Overloaded {\n" + + " public void foo() {}\n" + + " public void foo(String s) {}\n" + + "}", false, "java.lang.String"); + } + + public void testOverloadedMethodPublicFirst() { + doTestOverloadedMethod("foo", + "class Overloaded {\n" + + " public void foo() {}\n" + + " void foo(String s) {}\n" + + "}", false); + } + + public void testOverloadedMethodPublicSecond() { + doTestOverloadedMethod("foo", + "class Overloaded {\n" + + " void foo() {}\n" + + " public void foo(String s) {}\n" + + "}", false, "java.lang.String"); + } + + public void testOverloadedDeclaredMethod() { + doTestOverloadedMethod("foo", + "class Overloaded {\n" + + " public void foo() {}\n" + + " public void foo(String s) {}\n" + + "}", true, "java.lang.String"); + } + + public void testOverloadedInheritedMethod() { + doTestOverloadedMethod("bar", + "class OverloadedParent {" + + " public void bar(String s) {}\n" + + "}" + + "" + + "class Overloaded extends OverloadedParent {\n" + + " public void bar() {}\n" + + "}", false, "java.lang.String"); + } + + private void doTestOverloadedMethod(String name, + @NotNull @NonNls @Language("JAVA") String classText, + boolean isDeclared, + String... expectedParameterTypes) { + myFixture.addClass(classText); + + PsiMember member = doTestImpl(name, + "class Main {" + + " void main() {" + + " Overloaded.class.get" + (isDeclared?"Declared":"") + "Method(\""+name+"\", String.class);" + + " }" + + "}"); + assertTrue("Target is a method", member instanceof PsiMethod); + PsiMethod method = (PsiMethod)member; + PsiParameter[] parameters = method.getParameterList().getParameters(); + assertEquals("Parameter count", expectedParameterTypes.length, parameters.length); + for (int i = 0; i < expectedParameterTypes.length; i++) { + assertEquals("Parameter type " + i, expectedParameterTypes[i], parameters[0].getType().getCanonicalText()); + } + } + private void doTest(String name, @MagicConstant(stringValues = {FIELD, METHOD, DF, DM}) String type) { @@ -104,7 +166,7 @@ public class JavaReflectionNavigationTest extends LightCodeInsightFixtureTestCas doTestImpl(name, mainClassText); } - private void doTestImpl(String name, String mainClassText) { + private PsiMember doTestImpl(String name, @NotNull @NonNls @Language("JAVA") String mainClassText) { PsiReference reference = getReference(mainClassText); assertEquals("Reference text", name, reference.getCanonicalText()); PsiElement resolved = reference.resolve(); @@ -112,6 +174,7 @@ public class JavaReflectionNavigationTest extends LightCodeInsightFixtureTestCas assertTrue("Target is a member", resolved instanceof PsiMember); PsiMember member = (PsiMember)resolved; assertEquals("Target name", name, member.getName()); + return member; } private void doNegativeTest(String name, diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java index ee1d16172c03..062f0c1a91aa 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTest.java @@ -360,6 +360,17 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase { myFixture.enableInspections(inspection); myFixture.testHighlighting(true, false, true, getTestName(false) + ".java"); } + + public void testTypeQualifierNickname() { + addJavaxNullabilityAnnotations(myFixture); + + myFixture.addClass("package bar;" + + "import javax.annotation.meta.*;" + + "@TypeQualifierNickname() @javax.annotation.NonNull(when = Maybe.MAYBE) " + + "public @interface NullableNick {}"); + + doTest(); + } public static void addJavaxDefaultNullabilityAnnotations(final JavaCodeInsightTestFixture fixture) { fixture.addClass("package javax.annotation;" + @@ -371,12 +382,23 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase { } public static void addJavaxNullabilityAnnotations(final JavaCodeInsightTestFixture fixture) { - fixture.addClass("package javax.annotation;" + - "public @interface Nonnull {}"); - fixture.addClass("package javax.annotation;" + - "public @interface Nullable {}"); fixture.addClass("package javax.annotation.meta;" + "public @interface TypeQualifierDefault { java.lang.annotation.ElementType[] value() default {};}"); + fixture.addClass("package javax.annotation.meta;" + + "public enum When { ALWAYS, UNKNOWN, MAYBE, NEVER }"); + fixture.addClass("package javax.annotation.meta;" + + "public @interface TypeQualifierNickname {}"); + + fixture.addClass("package javax.annotation;" + + "import javax.annotation.meta.*;" + + "public @interface Nonnull {" + + " When when() default When.ALWAYS;" + + "}"); + fixture.addClass("package javax.annotation;" + + "import javax.annotation.meta.*;" + + "@TypeQualifierNickname " + + "@Nonnull(when = When.UNKNOWN) " + + "public @interface Nullable {}"); } public void testCustomTypeQualifierDefault() { diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/intentions/ConvertFieldToThreadLocalIntention.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/intentions/ConvertFieldToThreadLocalIntention.java index 6161a42af635..0e03d91dd617 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/intentions/ConvertFieldToThreadLocalIntention.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/intentions/ConvertFieldToThreadLocalIntention.java @@ -115,7 +115,9 @@ public class ConvertFieldToThreadLocalIntention extends PsiElementBaseIntentionA } PsiExpression initializer = psiField.getInitializer(); - if (initializer == null) { + + if (initializer == null && + !psiField.hasModifierProperty(PsiModifier.FINAL)) { final PsiType type = psiField.getType(); String initializerText = null; if (PsiType.BOOLEAN.equals(type)) { diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/AtomicConversionRule.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/AtomicConversionRule.java index 49047421571e..86be70f05ee9 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/AtomicConversionRule.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/AtomicConversionRule.java @@ -12,6 +12,8 @@ import com.intellij.refactoring.typeMigration.TypeConversionDescriptor; import com.intellij.refactoring.typeMigration.TypeConversionDescriptorBase; import com.intellij.refactoring.typeMigration.TypeEvaluator; import com.intellij.refactoring.typeMigration.TypeMigrationLabeler; +import com.intellij.util.ObjectUtils; +import com.siyeh.HardcodedMethodConstants; import com.siyeh.ig.psiutils.ParenthesesUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -342,6 +344,9 @@ public class AtomicConversionRule extends TypeConversionRule { if (context instanceof PsiArrayAccessExpression) { return new TypeConversionDescriptor("$qualifier$[$idx$]", "$qualifier$.get($idx$)", (PsiExpression)context); } + if (parent instanceof PsiReferenceExpression && isReferenceToLengthField((PsiReferenceExpression)parent)) { + return new TypeConversionDescriptor("$qualifier$.length", "$qualifier$.length()", (PsiExpression)parent); + } return null; } @@ -438,4 +443,13 @@ public class AtomicConversionRule extends TypeConversionRule { return null; } + private static boolean isReferenceToLengthField(@NotNull PsiReferenceExpression refExpr) { + if (!"length".equals(refExpr.getReferenceName())) { + return false; + } + PsiClass aClass = JavaPsiFacade.getElementFactory(refExpr.getProject()).getArrayClass(PsiUtil.getLanguageLevel(refExpr)); + PsiField lengthField = ObjectUtils.notNull(aClass.findFieldByName(HardcodedMethodConstants.LENGTH, false)); + return refExpr.isReferenceTo(lengthField); + } + } diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/ThreadLocalConversionRule.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/ThreadLocalConversionRule.java index e403dfb45e99..bf7e2cc746aa 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/ThreadLocalConversionRule.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/ThreadLocalConversionRule.java @@ -1,5 +1,7 @@ package com.intellij.refactoring.typeMigration.rules; +import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil; +import com.intellij.codeInsight.daemon.impl.quickfix.VariableAccessFromInnerClassFix; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Comparing; import com.intellij.psi.*; @@ -8,10 +10,14 @@ import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.TypeConversionUtil; import com.intellij.refactoring.typeMigration.TypeConversionDescriptor; import com.intellij.refactoring.typeMigration.TypeConversionDescriptorBase; +import com.intellij.refactoring.typeMigration.TypeEvaluator; import com.intellij.refactoring.typeMigration.TypeMigrationLabeler; +import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.List; + public class ThreadLocalConversionRule extends TypeConversionRule { private static final Logger LOG = Logger.getInstance(ThreadLocalConversionRule.class); @@ -150,24 +156,39 @@ public class ThreadLocalConversionRule extends TypeConversionRule { public static TypeConversionDescriptor wrapWithNewExpression(PsiType to, PsiType from, PsiExpression initializer) { final String boxedTypeName = from instanceof PsiPrimitiveType ? ((PsiPrimitiveType)from).getBoxedTypeName() : from.getCanonicalText(); - return new TypeConversionDescriptor("$qualifier$", "new " + - to.getCanonicalText() + - "() {\n" + - "@Override \n" + - "protected " + - boxedTypeName + - " initialValue() {\n" + - " return " + - (PsiUtil.isLanguageLevel5OrHigher(initializer) - ? initializer.getText() - : (from instanceof PsiPrimitiveType ? "new " + - ((PsiPrimitiveType)from).getBoxedTypeName() + - "(" + - initializer.getText() + - ")" : initializer.getText())) + - ";\n" + - "}\n" + - "}", initializer); + List toMakeFinal = TypeConversionRuleUtil.getVariablesToMakeFinal(initializer); + if (toMakeFinal == null) return null; + return new WrappingWithInnerClassOrLambdaDescriptor("$qualifier$", + createThreadLocalInitializerReplacement(to, from, initializer, boxedTypeName), + initializer, + toMakeFinal); + } + + private static String createThreadLocalInitializerReplacement(PsiType to, + PsiType from, + PsiExpression initializer, + String boxedTypeName) { + if (PsiUtil.isLanguageLevel8OrHigher(initializer)) { + return "java.lang.ThreadLocal.withInitial(() -> " + initializer.getText() + ")"; + } + return "new " + + to.getCanonicalText() + + "() {\n" + + "@Override \n" + + "protected " + + boxedTypeName + + " initialValue() {\n" + + " return " + + (PsiUtil.isLanguageLevel5OrHigher(initializer) + ? initializer.getText() + : (from instanceof PsiPrimitiveType ? "new " + + ((PsiPrimitiveType)from).getBoxedTypeName() + + "(" + + initializer.getText() + + ")" : initializer.getText())) + + ";\n" + + "}\n" + + "}"; } private static String toPrimitive(String replaceByArg, PsiType from, PsiElement context) { @@ -221,5 +242,27 @@ public class ThreadLocalConversionRule extends TypeConversionRule { return toBoxed(arg, from, context); } + private static class WrappingWithInnerClassOrLambdaDescriptor extends TypeConversionDescriptor { + private final List myVariablesToMakeFinal; + private WrappingWithInnerClassOrLambdaDescriptor(@NonNls final String stringToReplace, + @NonNls final String replaceByString, + final PsiExpression expression, + @NotNull List toMakeFinal) { + super(stringToReplace, replaceByString, expression); + myVariablesToMakeFinal = toMakeFinal; + } + + @Override + public PsiExpression replace(PsiExpression expression, @NotNull TypeEvaluator evaluator) { + PsiExpression replaced = super.replace(expression, evaluator); + boolean atLeastJava8 = PsiUtil.isLanguageLevel8OrHigher(replaced); + for (PsiVariable var : myVariablesToMakeFinal) { + if (!atLeastJava8 || !HighlightControlFlowUtil.isEffectivelyFinal(var, replaced, null)) { + VariableAccessFromInnerClassFix.fixAccess(var, replaced); + } + } + return replaced; + } + } } \ No newline at end of file diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/TypeConversionRuleUtil.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/TypeConversionRuleUtil.java new file mode 100644 index 000000000000..6b4af6fdefa7 --- /dev/null +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/TypeConversionRuleUtil.java @@ -0,0 +1,74 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.refactoring.typeMigration.rules; + +import com.intellij.psi.*; +import com.intellij.psi.controlFlow.*; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +class TypeConversionRuleUtil { + static List getVariablesToMakeFinal(@NotNull PsiExpression expression) { + final ControlFlow controlFlow; + try { + controlFlow = ControlFlowFactory.getInstance(expression.getProject()).getControlFlow(expression, new MyControlFlowPolicy(expression), false, false); + } + catch (AnalysisCanceledException e) { + return null; + } + + Collection writtenVariables = ControlFlowUtil.getWrittenVariables(controlFlow, 0, controlFlow.getSize(), false); + if (!writtenVariables.isEmpty()) return null; + + return ControlFlowUtil.getUsedVariables(controlFlow, 0, controlFlow.getSize()) + .stream() + .filter(v -> !v.hasModifierProperty(PsiModifier.FINAL)) + .collect(Collectors.toList()); + } + + private static class MyControlFlowPolicy implements ControlFlowPolicy { + private final PsiElement myElement; + + public MyControlFlowPolicy(PsiElement element) {myElement = element;} + + @Override + public PsiVariable getUsedVariable(@NotNull PsiReferenceExpression refExpr) { + if (refExpr.isQualified()) return null; + + PsiElement refElement = refExpr.resolve(); + if ((refElement instanceof PsiLocalVariable || refElement instanceof PsiParameter) && + !PsiTreeUtil.isAncestor(myElement, refElement, true)) { + return (PsiVariable) refElement; + } + + return null; + } + + @Override + public boolean isParameterAccepted(@NotNull PsiParameter psiParameter) { + return true; + } + + @Override + public boolean isLocalVariableAccepted(@NotNull PsiLocalVariable psiVariable) { + return true; + } + } +} diff --git a/java/typeMigration/test/com/intellij/codeInsight/ConvertToThreadLocalIntentionTest.java b/java/typeMigration/test/com/intellij/codeInsight/ConvertToThreadLocalIntentionTest.java index e45b26ba77c8..6a358bbbb2d1 100644 --- a/java/typeMigration/test/com/intellij/codeInsight/ConvertToThreadLocalIntentionTest.java +++ b/java/typeMigration/test/com/intellij/codeInsight/ConvertToThreadLocalIntentionTest.java @@ -8,11 +8,6 @@ import org.jetbrains.annotations.NotNull; * @author anna */ public class ConvertToThreadLocalIntentionTest extends LightQuickFixParameterizedTestCase { - @Override - protected boolean shouldBeAvailableAfterExecution() { - return true; - } - @Override protected String getBasePath() { return "/intentions/threadLocal"; diff --git a/java/typeMigration/testData/intentions/atomic/afterAtomicArrayLength.java b/java/typeMigration/testData/intentions/atomic/afterAtomicArrayLength.java new file mode 100644 index 000000000000..76bd5940cb3a --- /dev/null +++ b/java/typeMigration/testData/intentions/atomic/afterAtomicArrayLength.java @@ -0,0 +1,10 @@ +import java.util.concurrent.atomic.AtomicIntegerArray; + +// "Convert to atomic" "true" +class Test { + final AtomicIntegerArray ii = new AtomicIntegerArray(new int[12]); + + void m() { + int k = ii.length(); + } +} diff --git a/java/typeMigration/testData/intentions/atomic/beforeAtomicArrayLength.java b/java/typeMigration/testData/intentions/atomic/beforeAtomicArrayLength.java new file mode 100644 index 000000000000..01fa0f5b0866 --- /dev/null +++ b/java/typeMigration/testData/intentions/atomic/beforeAtomicArrayLength.java @@ -0,0 +1,8 @@ +// "Convert to atomic" "true" +class Test { + int[] ii = new int[12]; + + void m() { + int k = ii.length; + } +} diff --git a/java/typeMigration/testData/intentions/threadLocal/after1.java b/java/typeMigration/testData/intentions/threadLocal/after1.java index b5df8fd35214..be391d4abec4 100644 --- a/java/typeMigration/testData/intentions/threadLocal/after1.java +++ b/java/typeMigration/testData/intentions/threadLocal/after1.java @@ -1,11 +1,6 @@ // "Convert to ThreadLocal" "true" class Test { - final ThreadLocal field = new ThreadLocal() { - @Override - protected Integer initialValue() { - return 0; - } - }; + final ThreadLocal field = ThreadLocal.withInitial(() -> 0); void foo() { field.set(field.get() + 1); } diff --git a/java/typeMigration/testData/intentions/threadLocal/after2.java b/java/typeMigration/testData/intentions/threadLocal/after2.java index 3a546c5b0c9a..5f02c2f3925d 100644 --- a/java/typeMigration/testData/intentions/threadLocal/after2.java +++ b/java/typeMigration/testData/intentions/threadLocal/after2.java @@ -1,11 +1,6 @@ // "Convert to ThreadLocal" "true" class Test { - final ThreadLocal field = new ThreadLocal() { - @Override - protected String initialValue() { - return ""; - } - }; + final ThreadLocal field = ThreadLocal.withInitial(() -> ""); void foo() { System.out.println(field.get()); } diff --git a/java/typeMigration/testData/intentions/threadLocal/after3.java b/java/typeMigration/testData/intentions/threadLocal/after3.java index 9fc0f825ac4b..2e2f950d899a 100644 --- a/java/typeMigration/testData/intentions/threadLocal/after3.java +++ b/java/typeMigration/testData/intentions/threadLocal/after3.java @@ -1,11 +1,6 @@ // "Convert to ThreadLocal" "true" class Test { - final ThreadLocal field = new ThreadLocal() { - @Override - protected Integer initialValue() { - return new Integer(0); - } - }; + final ThreadLocal field = ThreadLocal.withInitial(() -> new Integer(0)); void foo() { if (field.get() == null) return; } diff --git a/java/typeMigration/testData/intentions/threadLocal/after4.java b/java/typeMigration/testData/intentions/threadLocal/after4.java index 8410a1c33c0e..4dbc67bb1b47 100644 --- a/java/typeMigration/testData/intentions/threadLocal/after4.java +++ b/java/typeMigration/testData/intentions/threadLocal/after4.java @@ -1,11 +1,6 @@ // "Convert to ThreadLocal" "true" class Test { - final ThreadLocal field = new ThreadLocal() { - @Override - protected String initialValue() { - return ""; - } - }; + final ThreadLocal field = ThreadLocal.withInitial(() -> ""); void foo() { if (field.get().indexOf("a") == -1) return; } diff --git a/java/typeMigration/testData/intentions/threadLocal/after5.java b/java/typeMigration/testData/intentions/threadLocal/after5.java index 435e1b6a50e6..31ca64fbaaef 100644 --- a/java/typeMigration/testData/intentions/threadLocal/after5.java +++ b/java/typeMigration/testData/intentions/threadLocal/after5.java @@ -1,11 +1,6 @@ // "Convert to ThreadLocal" "true" class Test { - final ThreadLocal field = new ThreadLocal() { - @Override - protected Integer initialValue() { - return new Integer(0); - } - }; + final ThreadLocal field = ThreadLocal.withInitial(() -> new Integer(0)); void foo(Test t) { if (t.field.get() == null) return; } diff --git a/java/typeMigration/testData/intentions/threadLocal/after6.java b/java/typeMigration/testData/intentions/threadLocal/after6.java index 1879a46be16d..d153a6579061 100644 --- a/java/typeMigration/testData/intentions/threadLocal/after6.java +++ b/java/typeMigration/testData/intentions/threadLocal/after6.java @@ -2,11 +2,6 @@ class Test { static final ThreadLocal field; static { - field = new ThreadLocal() { - @Override - protected Integer initialValue() { - return new Integer(0); - } - }; + field = ThreadLocal.withInitial(() -> new Integer(0)); } } \ No newline at end of file diff --git a/java/typeMigration/testData/intentions/threadLocal/after7.java b/java/typeMigration/testData/intentions/threadLocal/after7.java index 5398ff5e2991..14fa782d7060 100644 --- a/java/typeMigration/testData/intentions/threadLocal/after7.java +++ b/java/typeMigration/testData/intentions/threadLocal/after7.java @@ -1,11 +1,6 @@ // "Convert to ThreadLocal" "true" class X { - private final ThreadLocal bytes = new ThreadLocal() { - @Override - protected byte[] initialValue() { - return new byte[10]; - } - }; + private final ThreadLocal bytes = ThreadLocal.withInitial(() -> new byte[10]); byte foo(byte b) { bytes.get()[0] = 1; diff --git a/java/typeMigration/testData/intentions/threadLocal/after8.java b/java/typeMigration/testData/intentions/threadLocal/after8.java index d158894b6130..2d4aa4b61658 100644 --- a/java/typeMigration/testData/intentions/threadLocal/after8.java +++ b/java/typeMigration/testData/intentions/threadLocal/after8.java @@ -1,9 +1,4 @@ // "Convert to ThreadLocal" "true" class X { - final ThreadLocal i = new ThreadLocal() { - @Override - protected Integer initialValue() { - return 0; - } - }; + final ThreadLocal i = ThreadLocal.withInitial(() -> 0); } \ No newline at end of file diff --git a/java/typeMigration/testData/intentions/threadLocal/afterArrayInitializer.java b/java/typeMigration/testData/intentions/threadLocal/afterArrayInitializer.java index 59323f18e0e5..dde597cbc1da 100644 --- a/java/typeMigration/testData/intentions/threadLocal/afterArrayInitializer.java +++ b/java/typeMigration/testData/intentions/threadLocal/afterArrayInitializer.java @@ -1,9 +1,4 @@ // "Convert to ThreadLocal" "true" class Test { - final ThreadLocal field = new ThreadLocal() { - @Override - protected String[] initialValue() { - return new String[]{}; - } - }; + final ThreadLocal field = ThreadLocal.withInitial(() -> new String[]{}); } \ No newline at end of file diff --git a/java/typeMigration/testData/intentions/threadLocal/afterFieldAssignmentFromNonFinalParameter.java b/java/typeMigration/testData/intentions/threadLocal/afterFieldAssignmentFromNonFinalParameter.java new file mode 100644 index 000000000000..654691a7f2c9 --- /dev/null +++ b/java/typeMigration/testData/intentions/threadLocal/afterFieldAssignmentFromNonFinalParameter.java @@ -0,0 +1,12 @@ +// "Convert to ThreadLocal" "true" +class Main { + private final ThreadLocal property; + + Main3(boolean property) { + if (property) { + property = false; + } + boolean finalProperty = property; + this.property = ThreadLocal.withInitial(() -> finalProperty); + } +} \ No newline at end of file diff --git a/java/typeMigration/testData/intentions/threadLocal/afterFinalField.java b/java/typeMigration/testData/intentions/threadLocal/afterFinalField.java new file mode 100644 index 000000000000..da8f5235f329 --- /dev/null +++ b/java/typeMigration/testData/intentions/threadLocal/afterFinalField.java @@ -0,0 +1,8 @@ +// "Convert to ThreadLocal" "true" +class Foo { + private final ThreadLocal property; + + Foo(boolean property) { + this.property = ThreadLocal.withInitial(() -> property); + } +} \ No newline at end of file diff --git a/java/typeMigration/testData/intentions/threadLocal/afterJava6.java b/java/typeMigration/testData/intentions/threadLocal/afterJava6.java new file mode 100644 index 000000000000..77bd711defff --- /dev/null +++ b/java/typeMigration/testData/intentions/threadLocal/afterJava6.java @@ -0,0 +1,13 @@ +// "Convert to ThreadLocal" "true" +class Main { + private final ThreadLocal property; + + Main3(final boolean property) { + this.property = new ThreadLocal() { + @Override + protected Boolean initialValue() { + return property; + } + }; + } +} \ No newline at end of file diff --git a/java/typeMigration/testData/intentions/threadLocal/afterNormalize.java b/java/typeMigration/testData/intentions/threadLocal/afterNormalize.java index 1b2532d9d1de..9c7a8342ebe8 100644 --- a/java/typeMigration/testData/intentions/threadLocal/afterNormalize.java +++ b/java/typeMigration/testData/intentions/threadLocal/afterNormalize.java @@ -1,11 +1,6 @@ // "Convert to ThreadLocal" "true" class X { - private final ThreadLocal s = new ThreadLocal() { - @Override - protected String initialValue() { - return ""; - } - }; + private final ThreadLocal s = ThreadLocal.withInitial(() -> ""); private String t; private String u; } \ No newline at end of file diff --git a/java/typeMigration/testData/intentions/threadLocal/afterPrimitiveNoInitializer.java b/java/typeMigration/testData/intentions/threadLocal/afterPrimitiveNoInitializer.java index b922e385cde7..2560a2a708d6 100644 --- a/java/typeMigration/testData/intentions/threadLocal/afterPrimitiveNoInitializer.java +++ b/java/typeMigration/testData/intentions/threadLocal/afterPrimitiveNoInitializer.java @@ -1,9 +1,4 @@ // "Convert to ThreadLocal" "true" class Test { - final ThreadLocal field = new ThreadLocal() { - @Override - protected Integer initialValue() { - return 0; - } - }; + final ThreadLocal field = ThreadLocal.withInitial(() -> 0); } \ No newline at end of file diff --git a/java/typeMigration/testData/intentions/threadLocal/afterTA1.java b/java/typeMigration/testData/intentions/threadLocal/afterTA1.java index 0e3687e146b6..d1da14e75739 100644 --- a/java/typeMigration/testData/intentions/threadLocal/afterTA1.java +++ b/java/typeMigration/testData/intentions/threadLocal/afterTA1.java @@ -5,10 +5,5 @@ import java.lang.annotation.*; public @interface TA { int value(); } class Test { - final ThreadLocal<@TA(42) Integer> field = new ThreadLocal() { - @Override - protected Integer initialValue() { - return 0; - } - }; + final ThreadLocal<@TA(42) Integer> field = ThreadLocal.withInitial(() -> 0); } \ No newline at end of file diff --git a/java/typeMigration/testData/intentions/threadLocal/beforeFieldAssignmentFromNonFinalParameter.java b/java/typeMigration/testData/intentions/threadLocal/beforeFieldAssignmentFromNonFinalParameter.java new file mode 100644 index 000000000000..48a644f90053 --- /dev/null +++ b/java/typeMigration/testData/intentions/threadLocal/beforeFieldAssignmentFromNonFinalParameter.java @@ -0,0 +1,11 @@ +// "Convert to ThreadLocal" "true" +class Main { + private final boolean property; + + Main3(boolean property) { + if (property) { + property = false; + } + this.property = property; + } +} \ No newline at end of file diff --git a/java/typeMigration/testData/intentions/threadLocal/beforeFinalField.java b/java/typeMigration/testData/intentions/threadLocal/beforeFinalField.java new file mode 100644 index 000000000000..1cffae690828 --- /dev/null +++ b/java/typeMigration/testData/intentions/threadLocal/beforeFinalField.java @@ -0,0 +1,8 @@ +// "Convert to ThreadLocal" "true" +class Foo { + private final boolean property; + + Foo(boolean property) { + this.property = property; + } +} \ No newline at end of file diff --git a/java/typeMigration/testData/intentions/threadLocal/beforeJava6.java b/java/typeMigration/testData/intentions/threadLocal/beforeJava6.java new file mode 100644 index 000000000000..acc57b15ecde --- /dev/null +++ b/java/typeMigration/testData/intentions/threadLocal/beforeJava6.java @@ -0,0 +1,8 @@ +// "Convert to ThreadLocal" "true" +class Main { + private final boolean property; + + Main3(boolean property) { + this.property = property; + } +} \ No newline at end of file diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsBranchPopup.java b/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsBranchPopup.java index 1f3a87c7917f..6253c4b9be90 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsBranchPopup.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsBranchPopup.java @@ -34,7 +34,6 @@ import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.swing.*; import javax.swing.event.HyperlinkEvent; import java.util.List; @@ -65,7 +64,6 @@ public abstract class DvcsBranchPopup { ? " in " + DvcsUtil.getShortRepositoryName(currentRepository) : ""; myPopup = new BranchActionGroupPopup(title + myRepoTitleInfo, myProject, preselectActionCondition, createActions(), dimensionKey); initBranchSyncPolicyIfNotInitialized(); - setCurrentBranchInfo(); warnThatBranchesDivergedIfNeeded(); } @@ -86,11 +84,6 @@ public abstract class DvcsBranchPopup { } } - protected void setCurrentBranchInfo() { - String branchText = "Current branch : "; - myPopup.setAdText(branchText + myCurrentRepository.getCurrentBranchName(), SwingConstants.CENTER); - } - private void notifyAboutSyncedBranches() { String description = "You have several " + myVcs.getDisplayName() + " roots in the project and they all are checked out at the same branch. " + @@ -144,11 +137,15 @@ public abstract class DvcsBranchPopup { } private void warnThatBranchesDivergedIfNeeded() { - if (myRepositoryManager.moreThanOneRoot() && myMultiRootBranchConfig.diverged() && userWantsSyncControl()) { + if (isBranchesDiverged()) { myPopup.setWarning("Branches have diverged"); } } + protected boolean isBranchesDiverged() { + return myRepositoryManager.moreThanOneRoot() && myMultiRootBranchConfig.diverged() && userWantsSyncControl(); + } + @NotNull protected abstract DefaultActionGroup createRepositoriesActions(); diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsMultiRootBranchConfig.java b/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsMultiRootBranchConfig.java index ee24209167fa..d999bf5334c8 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsMultiRootBranchConfig.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/branch/DvcsMultiRootBranchConfig.java @@ -20,6 +20,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; +import java.util.function.Function; public abstract class DvcsMultiRootBranchConfig { @NotNull protected final Collection myRepositories; @@ -34,9 +35,14 @@ public abstract class DvcsMultiRootBranchConfig { @Nullable public String getCurrentBranch() { + return getCommonName(Repository::getCurrentBranchName); + } + + @Nullable + public String getCommonName(@NotNull Function nameSupplier) { String commonBranch = null; for (Repo repository : myRepositories) { - String branchName = repository.getCurrentBranchName(); + String branchName = nameSupplier.apply(repository); if (branchName == null) { return null; } diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java b/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java index 17445b9d0402..fdc42fc91cd3 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/ui/BranchActionGroupPopup.java @@ -15,6 +15,7 @@ */ package com.intellij.dvcs.ui; +import com.intellij.icons.AllIcons; import com.intellij.ide.DataManager; import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.actionSystem.*; @@ -28,10 +29,7 @@ import com.intellij.openapi.ui.popup.PopupStep; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.WindowStateService; import com.intellij.openapi.vcs.ui.FlatSpeedSearchPopup; -import com.intellij.ui.ErrorLabel; -import com.intellij.ui.JBColor; -import com.intellij.ui.ScrollingUtil; -import com.intellij.ui.SeparatorWithText; +import com.intellij.ui.*; import com.intellij.ui.components.panels.OpaquePanel; import com.intellij.ui.popup.KeepingPopupOpenAction; import com.intellij.ui.popup.PopupFactoryImpl; @@ -40,6 +38,7 @@ import com.intellij.ui.popup.list.IconListPopupRenderer; import com.intellij.ui.popup.list.ListPopupImpl; import com.intellij.ui.popup.list.ListPopupModel; import com.intellij.ui.popup.list.PopupListElementRenderer; +import com.intellij.util.FontUtil; import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.JBUI; @@ -60,11 +59,14 @@ import static com.intellij.util.ui.UIUtil.DEFAULT_VGAP; public class BranchActionGroupPopup extends FlatSpeedSearchPopup { private static final DataKey POPUP_MODEL = DataKey.create("VcsPopupModel"); + private Project myProject; private MyPopupListElementRenderer myListElementRenderer; private boolean myShown; - @NotNull private Dimension myPrevSize = JBUI.emptySize(); private boolean myUserSizeChanged; - private Project myProject; + private boolean myInternalSizeChanged; + private int myMeanRowHeight; + @Nullable private final String myKey; + @NotNull private Dimension myPrevSize = JBUI.emptySize(); public BranchActionGroupPopup(@NotNull String title, @NotNull Project project, @@ -76,27 +78,62 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup { myProject = project; DataManager.registerDataProvider(getList(), dataId -> POPUP_MODEL.is(dataId) ? getListModel() : null); installOnHoverIconsSupport(getListElementRenderer()); - if (dimensionKey != null) { - Dimension storedSize = WindowStateService.getInstance(myProject).getSizeFor(myProject, dimensionKey); + myKey = dimensionKey; + if (myKey != null) { + Dimension storedSize = WindowStateService.getInstance(myProject).getSizeFor(myProject, myKey); if (storedSize != null) { //set forced size before component is shown setSize(storedSize); } + createTitlePanelToolbar(myKey); } - trackDimensions(dimensionKey); + myMeanRowHeight = getList().getCellBounds(0, 0).height + UIUtil.getListCellVPadding() * 2; + } + + void createTitlePanelToolbar(@NotNull String dimensionKey) { + AnAction restoreDefaultSizeAction = + new DumbAwareAction("Restore Size", "Restore default size for widget", AllIcons.Vcs.RestoreDefaultSize) { + @Override + public void actionPerformed(AnActionEvent e) { + WindowStateService.getInstance(myProject).putSizeFor(myProject, dimensionKey, null); + myInternalSizeChanged = true; + pack(true, true); + } + + @Override + public void update(AnActionEvent e) { + e.getPresentation().setEnabledAndVisible(myUserSizeChanged || + WindowStateService.getInstance(myProject).getSizeFor(myProject, dimensionKey) != null); + } + }; + final ActionToolbar popupTitleToolbar = ActionManager.getInstance() + .createActionToolbar("BranchWidget", new DefaultActionGroup(restoreDefaultSizeAction), true); + final JComponent toolbarComponent = popupTitleToolbar.getComponent(); + popupTitleToolbar.setReservePlaceAutoPopupIcon(false); + toolbarComponent.setBorder(JBUI.Borders.emptyRight(2)); + toolbarComponent.setOpaque(false); + + getTitle().setButtonComponent(new ActiveComponent.Adapter() { + @Override + public JComponent getComponent() { + return toolbarComponent; + } + }, null); } //for child popups only private BranchActionGroupPopup(@Nullable WizardPopup aParent, @NotNull ListPopupStep aStep, @Nullable Object parentValue) { super(aParent, aStep, DataContext.EMPTY_CONTEXT, parentValue); + // don't store children popup userSize; + myKey = null; DataManager.registerDataProvider(getList(), dataId -> POPUP_MODEL.is(dataId) ? getListModel() : null); installOnHoverIconsSupport(getListElementRenderer()); - // don't store children popup userSize; - trackDimensions(null); } private void trackDimensions(@Nullable String dimensionKey) { - getComponent().addComponentListener(new ComponentAdapter() { + Window popupWindow = getPopupWindow(); + if (popupWindow == null) return; + popupWindow.addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { if (myShown) { @@ -118,7 +155,11 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup { private void processOnSizeChanged() { Dimension newSize = ObjectUtils.assertNotNull(getSize()); - if (myPrevSize.height < newSize.height) { + int preferredHeight = getComponent().getPreferredSize().height; + int realHeight = getComponent().getHeight(); + boolean shouldExpand = preferredHeight + myMeanRowHeight < realHeight; + boolean sizeWasIncreased = myPrevSize.height < newSize.height; + if (!myInternalSizeChanged && sizeWasIncreased && shouldExpand) { List mores = getMoreActions(); for (MoreAction more : mores) { if (!getList().getScrollableTracksViewportHeight()) break; @@ -129,7 +170,9 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup { } } myPrevSize = newSize; - myUserSizeChanged = true; + //ugly properties to distinguish user size changed from pack method call after Restore Size action performed + myUserSizeChanged = !myInternalSizeChanged; + myInternalSizeChanged = false; } @NotNull @@ -165,6 +208,7 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup { if (size != null) { myPrevSize = size; } + trackDimensions(myKey); } private static void createSpeedSearchActions(@NotNull ActionGroup actionGroup, @@ -246,12 +290,7 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup { @Override protected WizardPopup createPopup(WizardPopup parent, PopupStep step, Object parentValue) { - WizardPopup popup = createListPopupStep(parent, step, parentValue); - RootAction rootAction = getRootAction(parentValue); - if (rootAction != null) { - popup.setAdText((rootAction).getCaption()); - } - return popup; + return createListPopupStep(parent, step, parentValue); } private WizardPopup createListPopupStep(WizardPopup parent, PopupStep step, Object parentValue) { @@ -261,11 +300,6 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup { return super.createPopup(parent, step, parentValue); } - @Nullable - private static RootAction getRootAction(Object value) { - return getSpecificAction(value, RootAction.class); - } - private static T getSpecificAction(Object value, @NotNull Class clazz) { if (value instanceof PopupFactoryImpl.ActionItem) { AnAction action = ((PopupFactoryImpl.ActionItem)value).getAction(); @@ -290,6 +324,7 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup { private class MyPopupListElementRenderer extends PopupListElementRenderer implements IconListPopupRenderer { + private ErrorLabel myPrefixLabel; private ErrorLabel myInfoLabel; private IconComponent myIconLabel; @@ -328,26 +363,36 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup { } myIconLabel.setIcon(myDescriptor.getIconFor(value)); PopupElementWithAdditionalInfo additionalInfoAction = getSpecificAction(value, PopupElementWithAdditionalInfo.class); - String infoText = additionalInfoAction != null ? additionalInfoAction.getInfoText() : null; + updateInfoComponent(myPrefixLabel, additionalInfoAction != null ? additionalInfoAction.getPrefixInfo() : null, isSelected); + updateInfoComponent(myInfoLabel, additionalInfoAction != null ? additionalInfoAction.getInfoText() : null, isSelected); + } + + private void updateInfoComponent(@NotNull ErrorLabel infoLabel, @Nullable String infoText, boolean isSelected) { if (infoText != null) { - myInfoLabel.setVisible(true); - myInfoLabel.setText(infoText); + infoLabel.setVisible(true); + infoLabel.setText(infoText); if (isSelected) { - setSelected(myInfoLabel); + setSelected(infoLabel); } else { - myInfoLabel.setBackground(getBackground()); - myInfoLabel.setForeground(JBColor.GRAY); // different foreground than for other elements + infoLabel.setBackground(getBackground()); + infoLabel.setForeground(JBColor.GRAY); // different foreground than for other elements } } else { - myInfoLabel.setVisible(false); + infoLabel.setVisible(false); } } @Override protected JComponent createItemComponent() { + myPrefixLabel = new ErrorLabel(); + myPrefixLabel.setOpaque(true); + myPrefixLabel.setBorder(JBUI.Borders.empty(1, 1, 1, DEFAULT_HGAP)); + Font minusOneFont = FontUtil.minusOne(myPrefixLabel.getFont()); + myPrefixLabel.setFont(minusOneFont); + myTextLabel = new ErrorLabel(); myTextLabel.setOpaque(true); myTextLabel.setBorder(JBUI.Borders.empty(1)); @@ -355,15 +400,19 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup { myInfoLabel = new ErrorLabel(); myInfoLabel.setOpaque(true); myInfoLabel.setBorder(JBUI.Borders.empty(1, DEFAULT_HGAP, 1, 1)); + myInfoLabel.setFont(minusOneFont); JPanel compoundPanel = new OpaquePanel(new BorderLayout(), JBColor.WHITE); myIconLabel = new IconComponent(); myInfoLabel.setHorizontalAlignment(SwingConstants.RIGHT); + JPanel compoundTextPanel = new OpaquePanel(new BorderLayout(), compoundPanel.getBackground()); JPanel textPanel = new OpaquePanel(new BorderLayout(), compoundPanel.getBackground()); compoundPanel.add(myIconLabel, BorderLayout.WEST); textPanel.add(myTextLabel, BorderLayout.WEST); textPanel.add(myInfoLabel, BorderLayout.CENTER); - compoundPanel.add(textPanel, BorderLayout.CENTER); + compoundTextPanel.add(myPrefixLabel, BorderLayout.WEST); + compoundTextPanel.add(textPanel, BorderLayout.CENTER); + compoundPanel.add(compoundTextPanel, BorderLayout.CENTER); return layoutComponent(compoundPanel); } diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/ui/PopupElementWithAdditionalInfo.java b/platform/dvcs-impl/src/com/intellij/dvcs/ui/PopupElementWithAdditionalInfo.java index d59929095a9f..b7508b845dfb 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/ui/PopupElementWithAdditionalInfo.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/ui/PopupElementWithAdditionalInfo.java @@ -19,5 +19,8 @@ import org.jetbrains.annotations.Nullable; public interface PopupElementWithAdditionalInfo { @Nullable - String getInfoText(); + default String getInfoText() {return null;} + + @Nullable + default String getPrefixInfo() {return null;} } diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/ui/RootAction.java b/platform/dvcs-impl/src/com/intellij/dvcs/ui/RootAction.java index 7e9f5a22778f..e407d1a5704e 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/ui/RootAction.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/ui/RootAction.java @@ -31,9 +31,9 @@ public class RootAction extends ActionGroup implements Pop @NotNull protected final T myRepository; @NotNull private final ActionGroup myGroup; - @NotNull private final String myBranchText; + @Nullable private final String myBranchText; - public RootAction(@NotNull T repository, @NotNull ActionGroup actionsGroup, @NotNull String branchText) { + public RootAction(@NotNull T repository, @NotNull ActionGroup actionsGroup, @Nullable String branchText) { super("", true); myRepository = repository; myGroup = actionsGroup; @@ -41,18 +41,13 @@ public class RootAction extends ActionGroup implements Pop getTemplatePresentation().setText(DvcsUtil.getShortRepositoryName(repository), false); } - @NotNull - public String getCaption() { - return "Current branch in " + DvcsUtil.getShortRepositoryName(myRepository) + ": " + getInfoText(); - } - @NotNull @Override public AnAction[] getChildren(@Nullable AnActionEvent e) { return myGroup.getChildren(e); } - @NotNull + @Nullable @Override public String getInfoText() { return myBranchText; diff --git a/platform/icons/src/vcs/restoreDefaultSize.png b/platform/icons/src/vcs/restoreDefaultSize.png new file mode 100644 index 000000000000..c3dd16b7aa69 Binary files /dev/null and b/platform/icons/src/vcs/restoreDefaultSize.png differ diff --git a/platform/icons/src/vcs/restoreDefaultSize@2x.png b/platform/icons/src/vcs/restoreDefaultSize@2x.png new file mode 100644 index 000000000000..7b9dbc291778 Binary files /dev/null and b/platform/icons/src/vcs/restoreDefaultSize@2x.png differ diff --git a/platform/icons/src/vcs/restoreDefaultSize@2x_dark.png b/platform/icons/src/vcs/restoreDefaultSize@2x_dark.png new file mode 100644 index 000000000000..6a357c9e36b6 Binary files /dev/null and b/platform/icons/src/vcs/restoreDefaultSize@2x_dark.png differ diff --git a/platform/icons/src/vcs/restoreDefaultSize_dark.png b/platform/icons/src/vcs/restoreDefaultSize_dark.png new file mode 100644 index 000000000000..ed9d8114615c Binary files /dev/null and b/platform/icons/src/vcs/restoreDefaultSize_dark.png differ diff --git a/platform/lang-impl/src/com/intellij/application/options/colors/AbstractFontOptionsPanel.java b/platform/lang-impl/src/com/intellij/application/options/colors/AbstractFontOptionsPanel.java index 893da0cf4387..509478b12a95 100644 --- a/platform/lang-impl/src/com/intellij/application/options/colors/AbstractFontOptionsPanel.java +++ b/platform/lang-impl/src/com/intellij/application/options/colors/AbstractFontOptionsPanel.java @@ -23,7 +23,6 @@ import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.colors.FontPreferences; import com.intellij.openapi.editor.colors.ModifiableFontPreferences; -import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.util.SystemInfo; import com.intellij.ui.DocumentAdapter; import com.intellij.ui.FontComboBox; @@ -32,7 +31,6 @@ import com.intellij.ui.TooltipWithClickableLinks; import com.intellij.ui.components.JBCheckBox; import com.intellij.util.EventDispatcher; import com.intellij.util.ui.JBUI; -import net.miginfocom.swing.MigLayout; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -61,9 +59,8 @@ public abstract class AbstractFontOptionsPanel extends JPanel implements Options @NotNull private final JTextField myEditorFontSizeField = new JTextField(4); @NotNull private final JTextField myLineSpacingField = new JTextField(4); private final FontComboBox myPrimaryCombo = new FontComboBox(); - private final JCheckBox myUseSecondaryFontCheckbox = new JCheckBox(ApplicationBundle.message("secondary.font")); private final JCheckBox myEnableLigaturesCheckbox = new JCheckBox(ApplicationBundle.message("use.ligatures")); - private final FontComboBox mySecondaryCombo = new FontComboBox(false, false); + private final FontComboBox mySecondaryCombo = new FontComboBox(false, false, true); @NotNull private final JBCheckBox myOnlyMonospacedCheckBox = new JBCheckBox(ApplicationBundle.message("checkbox.show.only.monospaced.fonts")); @@ -72,30 +69,67 @@ public abstract class AbstractFontOptionsPanel extends JPanel implements Options private JLabel myPrimaryLabel; private JLabel mySizeLabel; + protected final static int ADDITIONAL_VERTICAL_GAP = 12; + protected final static int BASE_INSET = 5; + private JLabel mySecondaryFontLabel; + private JLabel myLineSpacingLabel; protected AbstractFontOptionsPanel() { - setLayout(new MigLayout("ins 0, gap 5, flowx")); - initControls(); + setLayout(new FlowLayout(FlowLayout.LEFT)); + add(createControls()); + } + + protected JComponent createControls() { + return createFontSettingsPanel(); } @SuppressWarnings("unchecked") - protected void initControls() { - add(myOnlyMonospacedCheckBox, "newline 10, sgx b, sx 2"); + protected final JPanel createFontSettingsPanel() { + JPanel fontPanel = new JPanel(new GridBagLayout()); + GridBagConstraints c = new GridBagConstraints(); + c.anchor = GridBagConstraints.WEST; + c.insets = JBUI.insets(BASE_INSET, BASE_INSET, 0, 0); + c.gridx = 0; + c.gridy = 0; myPrimaryLabel = new JLabel(ApplicationBundle.message("primary.font")); - add(myPrimaryLabel, "newline, ax right"); - add(myPrimaryCombo, "sgx b"); + fontPanel.add(myPrimaryLabel, c); + + c.gridx = 1; + fontPanel.add(myPrimaryCombo, c); + + c.gridx = 2; + fontPanel.add(myOnlyMonospacedCheckBox, c); + + c.gridx = 0; + c.gridy = 1; mySizeLabel = new JLabel(ApplicationBundle.message("editbox.font.size")); - add(mySizeLabel, "gapleft 20"); - add(myEditorFontSizeField); - add(new JLabel(ApplicationBundle.message("editbox.line.spacing")), "gapleft 20"); - add(myLineSpacingField); + fontPanel.add(mySizeLabel, c); + + c.gridx = 1; + fontPanel.add(myEditorFontSizeField, c); + + c.gridx = 0; + c.gridy = 2; + myLineSpacingLabel = new JLabel(ApplicationBundle.message("editbox.line.spacing")); + myLineSpacingLabel.setLabelFor(myLineSpacingField); + fontPanel.add(myLineSpacingLabel, c); + c.gridx = 1; + fontPanel.add(myLineSpacingField,c); + + c.gridy = 3; + c.gridx = 0; + c.insets = JBUI.insets(BASE_INSET + ADDITIONAL_VERTICAL_GAP, BASE_INSET, 0, 0); + mySecondaryFontLabel = new JLabel(ApplicationBundle.message("secondary.font")); + mySecondaryFontLabel.setLabelFor(mySecondaryCombo); + fontPanel.add(mySecondaryFontLabel, c); + c.gridx = 1; + fontPanel.add(mySecondaryCombo, c); + c.gridx = 2; + JLabel fallbackLabel = new JLabel(ApplicationBundle.message("label.fallback.fonts.list.description")); + fallbackLabel.setEnabled(false); + fontPanel.add(fallbackLabel, c); - add(new JLabel(ApplicationBundle.message("label.fallback.fonts.list.description"), - MessageType.INFO.getDefaultIcon(), - SwingConstants.LEFT), "newline, sx 5"); - add(myUseSecondaryFontCheckbox, "newline, ax right"); - add(mySecondaryCombo, "sgx b"); JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0)); myEnableLigaturesCheckbox.setBorder(null); panel.add(myEnableLigaturesCheckbox); @@ -108,10 +142,12 @@ public abstract class AbstractFontOptionsPanel extends JPanel implements Options warningIcon.setBorder(JBUI.Borders.emptyLeft(5)); warningIcon.setVisible(!SystemInfo.isJetBrainsJvm); panel.add(warningIcon); - add(panel, "newline, sx 2"); + c.gridx = 0; + c.gridy = 4; + c.gridwidth = 2; + fontPanel.add(panel, c); myOnlyMonospacedCheckBox.setBorder(null); - myUseSecondaryFontCheckbox.setBorder(null); mySecondaryCombo.setEnabled(false); myOnlyMonospacedCheckBox.setSelected(EditorColorsManager.getInstance().isUseOnlyMonospacedFonts()); @@ -126,10 +162,6 @@ public abstract class AbstractFontOptionsPanel extends JPanel implements Options mySecondaryCombo.setMonospacedOnly(myOnlyMonospacedCheckBox.isSelected()); mySecondaryCombo.setRenderer(RENDERER); - myUseSecondaryFontCheckbox.addActionListener(e -> { - mySecondaryCombo.setEnabled(myUseSecondaryFontCheckbox.isSelected()); - syncFontFamilies(); - }); ItemListener itemListener = this::syncFontFamilies; myPrimaryCombo.addItemListener(itemListener); mySecondaryCombo.addItemListener(itemListener); @@ -198,6 +230,7 @@ public abstract class AbstractFontOptionsPanel extends JPanel implements Options updateDescription(true); } }); + return fontPanel; } protected void setDelegatingPreferences(boolean isDelegating) { @@ -249,7 +282,7 @@ public abstract class AbstractFontOptionsPanel extends JPanel implements Options modifiableFontPreferences.clearFonts(); modifiableFontPreferences.setUseLigatures(myEnableLigaturesCheckbox.isSelected()); String primaryFontFamily = myPrimaryCombo.getFontName(); - String secondaryFontFamily = mySecondaryCombo.isEnabled() ? mySecondaryCombo.getFontName() : null; + String secondaryFontFamily = mySecondaryCombo.isNoFontSelected() ? null : mySecondaryCombo.getFontName(); int fontSize = getFontSizeFromField(); if (primaryFontFamily != null) { if (!FontPreferences.DEFAULT_FONT_NAME.equals(primaryFontFamily)) { @@ -276,7 +309,6 @@ public abstract class AbstractFontOptionsPanel extends JPanel implements Options List fontFamilies = fontPreferences.getEffectiveFontFamilies(); myPrimaryCombo.setFontName(fontPreferences.getFontFamily()); boolean isThereSecondaryFont = fontFamilies.size() > 1; - myUseSecondaryFontCheckbox.setSelected(isThereSecondaryFont); mySecondaryCombo.setFontName(isThereSecondaryFont ? fontFamilies.get(1) : null); myEditorFontSizeField.setText(String.valueOf(fontPreferences.getSize(fontPreferences.getFontFamily()))); @@ -285,12 +317,13 @@ public abstract class AbstractFontOptionsPanel extends JPanel implements Options boolean readOnly = isReadOnlyColorScheme || !(getFontPreferences() instanceof ModifiableFontPreferences); myPrimaryCombo.setEnabled(!readOnly); myPrimaryLabel.setEnabled(!readOnly); - mySecondaryCombo.setEnabled(isThereSecondaryFont && !readOnly); + mySecondaryCombo.setEnabled(!readOnly); + mySecondaryFontLabel.setEnabled(!readOnly); myOnlyMonospacedCheckBox.setEnabled(!readOnly); myLineSpacingField.setEnabled(!readOnly); + myLineSpacingLabel.setEnabled(!readOnly); myEditorFontSizeField.setEnabled(!readOnly); mySizeLabel.setEnabled(!readOnly); - myUseSecondaryFontCheckbox.setEnabled(!readOnly); myEnableLigaturesCheckbox.setEnabled(!readOnly && SystemInfo.isJetBrainsJvm); myEnableLigaturesCheckbox.setSelected(fontPreferences.useLigatures()); diff --git a/platform/lang-impl/src/com/intellij/application/options/colors/ConsoleFontOptions.java b/platform/lang-impl/src/com/intellij/application/options/colors/ConsoleFontOptions.java index 6a1f8fcd3c51..35c0886248c6 100644 --- a/platform/lang-impl/src/com/intellij/application/options/colors/ConsoleFontOptions.java +++ b/platform/lang-impl/src/com/intellij/application/options/colors/ConsoleFontOptions.java @@ -31,8 +31,13 @@ public class ConsoleFontOptions extends FontOptions { @Nullable @Override - protected String getInheritFontTitle() { - return "editor font"; + protected String getInheritedFontTitle() { + return "Scheme font"; + } + + @Override + protected String getOverwriteFontTitle() { + return "Set console font for color scheme"; } @Override @@ -55,6 +60,11 @@ public class ConsoleFontOptions extends FontOptions { return getCurrentScheme().getConsoleFontPreferences(); } + @Override + protected FontPreferences getBaseFontPreferences() { + return getCurrentScheme().getFontPreferences(); + } + @Override protected void setDelegatingPreferences(boolean isDelegating) { FontPreferences currPrefs = getCurrentScheme().getConsoleFontPreferences(); diff --git a/platform/lang-impl/src/com/intellij/application/options/colors/FontOptions.java b/platform/lang-impl/src/com/intellij/application/options/colors/FontOptions.java index 0f790fad6150..be16c024c59e 100644 --- a/platform/lang-impl/src/com/intellij/application/options/colors/FontOptions.java +++ b/platform/lang-impl/src/com/intellij/application/options/colors/FontOptions.java @@ -21,9 +21,13 @@ import com.intellij.ide.DataManager; import com.intellij.openapi.editor.colors.DelegatingFontPreferences; import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.editor.colors.FontPreferences; +import com.intellij.openapi.editor.colors.impl.AppEditorFontOptions; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.ex.Settings; import com.intellij.ui.HoverHyperlinkLabel; +import com.intellij.ui.JBColor; +import com.intellij.util.ui.JBDimension; +import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -39,24 +43,53 @@ public class FontOptions extends AbstractFontOptionsPanel { @NotNull private final ColorAndFontOptions myOptions; private @Nullable JCheckBox myInheritFontCheckbox; + private @Nullable JLabel myBaseFontInfoLabel; + + private final static int FONT_PANEL_LEFT_OFFSET = 15; public FontOptions(@NotNull ColorAndFontOptions options) { myOptions = options; } @Nullable - protected String getInheritFontTitle() { - return "default font"; + protected String getInheritedFontTitle() { + return "Default font"; + } + + protected String getOverwriteFontTitle() { + return "Set font for color scheme"; } @Override - protected void initControls() { - createInheritCheckBox(); - super.initControls(); + protected JComponent createControls() { + Component inheritBox = createInheritCheckBox(); + if (inheritBox != null) { + JPanel topPanel = new JPanel(new GridBagLayout()); + GridBagConstraints c = new GridBagConstraints(); + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 2; + c.insets = JBUI.insets(BASE_INSET, BASE_INSET, ADDITIONAL_VERTICAL_GAP, 0); + c.anchor = GridBagConstraints.LINE_START; + topPanel.add(inheritBox, c); + c.gridy = 1; + c.gridx = 0; + c.gridwidth = 1; + c.insets = JBUI.emptyInsets(); + topPanel.add(Box.createRigidArea(JBDimension.create(new Dimension(FONT_PANEL_LEFT_OFFSET, 0))), c); + c.gridx = 1; + c.anchor = GridBagConstraints.NORTHWEST; + topPanel.add(createFontSettingsPanel(), c); + return topPanel; + } + else { + return super.createControls(); + } } - private void createInheritCheckBox() { - if (getInheritFontTitle() != null) { + @Nullable + private Component createInheritCheckBox() { + if (getInheritedFontTitle() != null) { JPanel inheritPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0,0 )); inheritPanel.setBorder(BorderFactory.createEmptyBorder()); myInheritFontCheckbox = new JCheckBox(); @@ -68,17 +101,40 @@ public class FontOptions extends AbstractFontOptionsPanel { } }); inheritPanel.add(myInheritFontCheckbox); - inheritPanel.add(new JLabel("Use ")); - inheritPanel.add(createHyperlinkLabel()); - - add(inheritPanel, "newline, span"); - add(new JSeparator(), "newline, growx, span"); + inheritPanel.add(new JLabel(getOverwriteFontTitle())); + inheritPanel.add(Box.createRigidArea(JBDimension.create(new Dimension(5,0)))); + inheritPanel.add(grayed(new JLabel("("))); + inheritPanel.add(grayed(createHyperlinkLabel())); + inheritPanel.add(grayed(new JLabel(": "))); + myBaseFontInfoLabel = grayed(new JLabel("?")); + inheritPanel.add(myBaseFontInfoLabel); + inheritPanel.add(grayed(new JLabel(")"))); + return inheritPanel; } + return null; + } + + private static JLabel grayed(JLabel label) { + label.setForeground(JBColor.GRAY); + return label; + } + + private String getBaseFontInfo() { + StringBuilder sb = new StringBuilder(); + FontPreferences basePrefs = getBaseFontPreferences(); + sb.append(basePrefs.getFontFamily()); + sb.append(','); + sb.append(basePrefs.getSize(basePrefs.getFontFamily())); + return sb.toString(); + } + + protected FontPreferences getBaseFontPreferences() { + return AppEditorFontOptions.getInstance().getFontPreferences(); } @NotNull private JLabel createHyperlinkLabel() { - HoverHyperlinkLabel label = new HoverHyperlinkLabel(getInheritFontTitle()); + HoverHyperlinkLabel label = new HoverHyperlinkLabel(getInheritedFontTitle()); label.addHyperlinkListener(new HyperlinkListener() { @Override public void hyperlinkUpdate(HyperlinkEvent e) { @@ -154,6 +210,9 @@ public class FontOptions extends AbstractFontOptionsPanel { myInheritFontCheckbox.setEnabled(!isReadOnly()); myInheritFontCheckbox.setSelected(isDelegating()); } + if (myBaseFontInfoLabel != null) { + myBaseFontInfoLabel.setText(getBaseFontInfo()); + } } } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/CloseAction.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/CloseAction.java new file mode 100644 index 000000000000..2e7280024136 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/CloseAction.java @@ -0,0 +1,37 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInspection.ui; + +import com.intellij.CommonBundle; +import com.intellij.codeInspection.ex.GlobalInspectionContextImpl; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.DumbAware; + +class CloseAction extends AnAction implements DumbAware { + private GlobalInspectionContextImpl myContext; + + CloseAction(GlobalInspectionContextImpl context) { + super(CommonBundle.message("action.close"), null, AllIcons.Actions.Cancel); + myContext = context; + } + + @Override + public void actionPerformed(AnActionEvent e) { + myContext.close(true); + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java index ef815a16983b..66bc2ec0133a 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java @@ -16,7 +16,6 @@ package com.intellij.codeInspection.ui; -import com.intellij.CommonBundle; import com.intellij.ReviseWhenPortedToJDK; import com.intellij.analysis.AnalysisScope; import com.intellij.analysis.AnalysisUIOptions; @@ -30,7 +29,6 @@ import com.intellij.codeInspection.reference.RefEntity; import com.intellij.codeInspection.ui.actions.ExportHTMLAction; import com.intellij.codeInspection.ui.actions.InvokeQuickFixAction; import com.intellij.diff.util.DiffUtil; -import com.intellij.icons.AllIcons; import com.intellij.ide.*; import com.intellij.ide.actions.ContextHelpAction; import com.intellij.ide.actions.exclusion.ExclusionHandler; @@ -46,7 +44,6 @@ import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.colors.EditorColors; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.fileEditor.OpenFileDescriptor; -import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Splitter; import com.intellij.openapi.ui.popup.JBPopup; @@ -93,19 +90,14 @@ import java.util.List; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; -/** - * @author max - */ -public class InspectionResultsView extends JPanel implements Disposable, OccurenceNavigator, DataProvider { +public class InspectionResultsView extends JPanel implements Disposable, DataProvider { private static final Logger LOG = Logger.getInstance(InspectionResultsView.class); public static final DataKey DATA_KEY = DataKey.create("inspectionView"); private static final Key PREVIEW_EDITOR_IS_REUSED_KEY = Key.create("inspection.tool.window.preview.editor.is.reused"); - private final Project myProject; private final InspectionTree myTree; - private final ConcurrentMap> myGroups = - ContainerUtil.newConcurrentMap(); + private final ConcurrentMap> myGroups = ContainerUtil.newConcurrentMap(); private final OccurenceNavigator myOccurenceNavigator; private volatile InspectionProfileImpl myInspectionProfile; private final boolean mySettingsEnabled; @@ -144,7 +136,6 @@ public class InspectionResultsView extends JPanel implements Disposable, Occuren public InspectionResultsView(@NotNull GlobalInspectionContextImpl globalInspectionContext, @NotNull InspectionRVContentProvider provider) { setLayout(new BorderLayout()); - myProject = globalInspectionContext.getProject(); myInspectionProfile = globalInspectionContext.getCurrentProfile(); myScope = globalInspectionContext.getCurrentScope(); myGlobalInspectionContext = globalInspectionContext; @@ -152,12 +143,12 @@ public class InspectionResultsView extends JPanel implements Disposable, Occuren myExcludedInspectionTreeNodesManager = new ExcludedInspectionTreeNodesManager(provider instanceof OfflineInspectionRVContentProvider, isSingleInspectionRun()); - myTree = new InspectionTree(myProject, globalInspectionContext, this); + myTree = new InspectionTree(globalInspectionContext, this); initTreeListeners(); myOccurenceNavigator = initOccurenceNavigator(); - mySplitter = new OnePixelSplitter(false, AnalysisUIOptions.getInstance(myProject).SPLITTER_PROPORTION); + mySplitter = new OnePixelSplitter(false, AnalysisUIOptions.getInstance(globalInspectionContext.getProject()).SPLITTER_PROPORTION); mySplitter.setFirstComponent(ScrollPaneFactory.createScrollPane(myTree, SideBorder.LEFT)); mySplitter.setHonorComponentsMinimumSize(false); @@ -251,9 +242,9 @@ public class InspectionResultsView extends JPanel implements Disposable, Occuren } }; createActionsToolbar(); - PsiManager.getInstance(myProject).addPsiTreeChangeListener(new InspectionViewPsiTreeChangeAdapter(this), this); + PsiManager.getInstance(getProject()).addPsiTreeChangeListener(new InspectionViewPsiTreeChangeAdapter(this), this); - ProjectInspectionProfileManager profileManager = ProjectInspectionProfileManager.getInstance(myProject); + ProjectInspectionProfileManager profileManager = ProjectInspectionProfileManager.getInstance(getProject()); profileManager.addProfileChangeListener(new ProfileChangeAdapter() { @Override public void profileChanged(InspectionProfile profile) { @@ -364,12 +355,9 @@ public class InspectionResultsView extends JPanel implements Disposable, Occuren } private void createActionsToolbar() { - final JComponent leftActionsToolbar = createLeftActionsToolbar(); - final JComponent rightActionsToolbar = createRightActionsToolbar(); - JPanel westPanel = new JPanel(new BorderLayout()); - westPanel.add(leftActionsToolbar, BorderLayout.WEST); - westPanel.add(rightActionsToolbar, BorderLayout.EAST); + westPanel.add(createLeftActionsToolbar(), BorderLayout.WEST); + westPanel.add(createRightActionsToolbar(), BorderLayout.EAST); add(westPanel, BorderLayout.WEST); } @@ -389,8 +377,8 @@ public class InspectionResultsView extends JPanel implements Disposable, Occuren private JComponent createLeftActionsToolbar() { final CommonActionsManager actionsManager = CommonActionsManager.getInstance(); DefaultActionGroup group = new DefaultActionGroup(); - group.add(new RerunAction(this)); - group.add(new CloseAction()); + group.add(new RerunAction(this, this)); + group.add(new CloseAction(myGlobalInspectionContext)); final TreeExpander treeExpander = new DefaultTreeExpander(myTree); group.add(actionsManager.createExpandAllAction(treeExpander, myTree)); group.add(actionsManager.createCollapseAllAction(treeExpander, myTree)); @@ -419,9 +407,8 @@ public class InspectionResultsView extends JPanel implements Disposable, Occuren } } - private boolean isAutoScrollMode() { - String activeToolWindowId = ToolWindowManager.getInstance(myProject).getActiveToolWindowId(); + String activeToolWindowId = ToolWindowManager.getInstance(getProject()).getActiveToolWindowId(); return myGlobalInspectionContext.getUIOptions().AUTOSCROLL_TO_SOURCE && (activeToolWindowId == null || activeToolWindowId.equals(ToolWindowId.INSPECTION)); } @@ -436,27 +423,13 @@ public class InspectionResultsView extends JPanel implements Disposable, Occuren @Nullable private static OpenFileDescriptor getOpenFileDescriptor(final RefElement refElement) { - final VirtualFile[] file = new VirtualFile[1]; - final int[] offset = new int[1]; - - ApplicationManager.getApplication().runReadAction(() -> { - PsiElement psiElement = refElement.getElement(); - if (psiElement != null) { - final PsiFile containingFile = psiElement.getContainingFile(); - if (containingFile != null) { - file[0] = containingFile.getVirtualFile(); - offset[0] = psiElement.getTextOffset(); - } - } - else { - file[0] = null; - } - }); - - if (file[0] != null && file[0].isValid()) { - return new OpenFileDescriptor(refElement.getRefManager().getProject(), file[0], offset[0]); - } - return null; + PsiElement psiElement = refElement.getElement(); + if (psiElement == null) return null; + final PsiFile containingFile = psiElement.getContainingFile(); + if (containingFile == null) return null; + VirtualFile file = containingFile.getVirtualFile(); + if (file == null) return null; + return new OpenFileDescriptor(refElement.getRefManager().getProject(), file, psiElement.getTextOffset()); } public void setApplyingFix(boolean applyingFix) { @@ -601,7 +574,7 @@ public class InspectionResultsView extends JPanel implements Disposable, Occuren myPreviewEditor.getMarkupModel().removeAllHighlighters(); } else { - myPreviewEditor = (EditorEx)EditorFactory.getInstance().createEditor(document, myProject, file.getVirtualFile(), true); + myPreviewEditor = (EditorEx)EditorFactory.getInstance().createEditor(document, getProject(), file.getVirtualFile(), true); DiffUtil.setFoldingModelSupport(myPreviewEditor); final EditorSettings settings = myPreviewEditor.getSettings(); settings.setLineNumbersShown(false); @@ -621,7 +594,7 @@ public class InspectionResultsView extends JPanel implements Disposable, Occuren return Pair.create(myPreviewEditor.getComponent(), myPreviewEditor); } if (selectedEntity == null) { - return Pair.create(new InspectionNodeInfo(myTree, myProject), null); + return Pair.create(new InspectionNodeInfo(myTree, getProject()), null); } if (selectedEntity.isValid()) { return Pair.create(InspectionResultsViewUtil.getPreviewIsNotAvailable(selectedEntity), null); @@ -790,7 +763,7 @@ public class InspectionResultsView extends JPanel implements Disposable, Occuren InspectionToolWrapper toolWrapper = state.getTool(); if (ReadAction.compute(() -> myProvider.checkReportedProblems(myGlobalInspectionContext, toolWrapper))) { addTool(toolWrapper, - profile.getErrorLevel(key, state.getScope(myProject), myProject), + profile.getErrorLevel(key, state.getScope(getProject()), getProject()), isGroupedBySeverity, singleInspectionRun); } @@ -849,7 +822,7 @@ public class InspectionResultsView extends JPanel implements Disposable, Occuren if (isGroupedBySeverity) { InspectionSeverityGroupNode severityGroupNode = mySeverityGroupNodes.get(level); if (severityGroupNode == null) { - InspectionSeverityGroupNode newNode = new InspectionSeverityGroupNode(myProject, level); + InspectionSeverityGroupNode newNode = new InspectionSeverityGroupNode(getProject(), level); severityGroupNode = ConcurrencyUtil.cacheOrGet(mySeverityGroupNodes, level, newNode); if (severityGroupNode == newNode) { InspectionTreeNode root = myTree.getRoot(); @@ -865,39 +838,9 @@ public class InspectionResultsView extends JPanel implements Disposable, Occuren return myOccurenceNavigator; } - @Override - public boolean hasNextOccurence() { - return myOccurenceNavigator != null && myOccurenceNavigator.hasNextOccurence(); - } - - @Override - public boolean hasPreviousOccurence() { - return myOccurenceNavigator != null && myOccurenceNavigator.hasPreviousOccurence(); - } - - @Override - public OccurenceInfo goNextOccurence() { - return myOccurenceNavigator != null ? myOccurenceNavigator.goNextOccurence() : null; - } - - @Override - public OccurenceInfo goPreviousOccurence() { - return myOccurenceNavigator != null ? myOccurenceNavigator.goPreviousOccurence() : null; - } - - @Override - public String getNextOccurenceActionName() { - return myOccurenceNavigator != null ? myOccurenceNavigator.getNextOccurenceActionName() : ""; - } - - @Override - public String getPreviousOccurenceActionName() { - return myOccurenceNavigator != null ? myOccurenceNavigator.getPreviousOccurenceActionName() : ""; - } - @NotNull public Project getProject() { - return myProject; + return myGlobalInspectionContext.getProject(); } @Override @@ -1008,7 +951,7 @@ public class InspectionResultsView extends JPanel implements Disposable, Occuren startOffset = textRange.getStartOffset(); } } - return new OpenFileDescriptor(myProject, virtualFile, startOffset); + return new OpenFileDescriptor(getProject(), virtualFile, startOffset); } return null; } @@ -1107,44 +1050,11 @@ public class InspectionResultsView extends JPanel implements Disposable, Occuren return myDisposed; } - private class CloseAction extends AnAction implements DumbAware { - private CloseAction() { - super(CommonBundle.message("action.close"), null, AllIcons.Actions.Cancel); - } - - @Override - public void actionPerformed(AnActionEvent e) { - myGlobalInspectionContext.close(true); - } - } - public void updateCurrentProfile() { final String name = myInspectionProfile.getName(); myInspectionProfile = myInspectionProfile.getProfileManager().getProfile(name); } - private class RerunAction extends AnAction { - RerunAction(JComponent comp) { - super(InspectionsBundle.message("inspection.action.rerun"), InspectionsBundle.message("inspection.action.rerun"), - AllIcons.Actions.Rerun); - registerCustomShortcutSet(CommonShortcuts.getRerun(), comp); - } - - @Override - public void update(AnActionEvent e) { - e.getPresentation().setEnabled(isRerunAvailable()); - } - - @Override - public void actionPerformed(AnActionEvent e) { - rerun(); - } - - private void rerun() { - InspectionResultsView.this.rerun(); - } - } - public boolean isRerunAvailable() { return !(myProvider instanceof OfflineInspectionRVContentProvider) && myScope.isValid(); } @@ -1152,7 +1062,7 @@ public class InspectionResultsView extends JPanel implements Disposable, Occuren public void rerun() { myRerun = true; if (myScope.isValid()) { - AnalysisUIOptions.getInstance(myProject).save(myGlobalInspectionContext.getUIOptions()); + AnalysisUIOptions.getInstance(getProject()).save(myGlobalInspectionContext.getUIOptions()); myGlobalInspectionContext.setTreeState(getTree().getTreeState()); myGlobalInspectionContext.doInspections(myScope); } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTree.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTree.java index a08fffb23132..90dccde763d4 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTree.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTree.java @@ -56,9 +56,9 @@ public class InspectionTree extends Tree { @NotNull private InspectionTreeState myState = new InspectionTreeState(); private boolean myQueueUpdate; - public InspectionTree(@NotNull Project project, - @NotNull GlobalInspectionContextImpl context, + public InspectionTree(@NotNull GlobalInspectionContextImpl context, @NotNull InspectionResultsView view) { + Project project = context.getProject(); setModel(new DefaultTreeModel(new InspectionRootNode(project, new InspectionTreeUpdater(view)))); myContext = context; myExcludedManager = view.getExcludedManager(); diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeState.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeState.java index 70a850707b5e..bf420dfb96a2 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeState.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeState.java @@ -63,14 +63,14 @@ public class InspectionTreeState { private static class InspectionTreeSelectionPath { private final Object[] myPath; - private final int[] myIndicies; + private final int[] myIndices; public InspectionTreeSelectionPath(TreePath path) { myPath = path.getPath(); - myIndicies = new int[myPath.length]; + myIndices = new int[myPath.length]; for (int i = 0; i < myPath.length - 1; i++) { InspectionTreeNode node = (InspectionTreeNode)myPath[i]; - myIndicies[i + 1] = getChildIndex(node, (InspectionTreeNode)myPath[i + 1]); + myIndices[i + 1] = getChildIndex(node, (InspectionTreeNode)myPath[i + 1]); } } @@ -116,8 +116,8 @@ public class InspectionTreeState { // Exactly same element not found. Trying to select somewhat near. int count = newRoot.getChildCount(); if (count > 0) { - if (myIndicies[idx] < count) { - newPath.add(newRoot.getChildAt(myIndicies[idx])); + if (myIndices[idx] < count) { + newPath.add(newRoot.getChildAt(myIndices[idx])); } else { newPath.add(newRoot.getChildAt(count - 1)); diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/RerunAction.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/RerunAction.java new file mode 100644 index 000000000000..2224c6a19cbd --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/RerunAction.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInspection.ui; + +import com.intellij.codeInspection.InspectionsBundle; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonShortcuts; + +import javax.swing.*; + +class RerunAction extends AnAction { + private final InspectionResultsView myView; + + RerunAction(InspectionResultsView view, JComponent comp) { + super(InspectionsBundle.message("inspection.action.rerun"), InspectionsBundle.message("inspection.action.rerun"), + AllIcons.Actions.Rerun); + myView = view; + registerCustomShortcutSet(CommonShortcuts.getRerun(), comp); + } + + @Override + public void update(AnActionEvent e) { + e.getPresentation().setEnabled(myView.isRerunAvailable()); + } + + @Override + public void actionPerformed(AnActionEvent e) { + rerun(); + } + + private void rerun() { + myView.rerun(); + } +} diff --git a/platform/lang-impl/src/com/intellij/ide/scratch/ScratchFileServiceImpl.java b/platform/lang-impl/src/com/intellij/ide/scratch/ScratchFileServiceImpl.java index c46db97f7546..a991bb85fafe 100644 --- a/platform/lang-impl/src/com/intellij/ide/scratch/ScratchFileServiceImpl.java +++ b/platform/lang-impl/src/com/intellij/ide/scratch/ScratchFileServiceImpl.java @@ -289,7 +289,7 @@ public class ScratchFileServiceImpl extends ScratchFileService implements Persis return VfsUtil.createChildSequent(LocalFileSystem.getInstance(), dir, fileName, StringUtil.notNullize(ext)); } else { - return dir.createChildData(LocalFileSystem.getInstance(), fileNameExt); + return dir.findOrCreateChildData(LocalFileSystem.getInstance(), fileNameExt); } }); } diff --git a/platform/platform-api/src/com/intellij/util/net/IOExceptionDialog.java b/platform/platform-api/src/com/intellij/util/net/IOExceptionDialog.java index 571ab8edc263..35dc36354d77 100644 --- a/platform/platform-api/src/com/intellij/util/net/IOExceptionDialog.java +++ b/platform/platform-api/src/com/intellij/util/net/IOExceptionDialog.java @@ -41,7 +41,6 @@ public class IOExceptionDialog extends DialogWrapper { myErrorLabel = new JTextArea(); myErrorLabel.setEditable(false); myErrorLabel.setText(errorText); - myErrorLabel.setColumns(120); myErrorLabel.setLineWrap(true); myErrorLabel.setWrapStyleWord(true); myErrorLabel.setFont(UIManager.getFont("Label.font")); diff --git a/platform/platform-impl/src/com/intellij/openapi/util/WindowStateServiceImpl.java b/platform/platform-impl/src/com/intellij/openapi/util/WindowStateServiceImpl.java index 67d2c9eb27ad..622e5231de38 100644 --- a/platform/platform-impl/src/com/intellij/openapi/util/WindowStateServiceImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/util/WindowStateServiceImpl.java @@ -26,11 +26,13 @@ import com.intellij.util.ui.UIUtil; import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.awt.*; import java.util.Map; import java.util.TreeMap; -import java.util.function.Supplier; +import java.util.function.BiFunction; +import java.util.function.Function; /** * @author Sergey.Malenkov @@ -219,9 +221,32 @@ abstract class WindowStateServiceImpl extends WindowStateService implements Pers private T getFor(Object object, @NotNull String key, @NotNull Class type) { GraphicsDevice screen = getScreen(object); - T state = get(getKey(screen, key), type); + float scale = getSysScale(screen); + + Function getState = (myKey) -> { + WindowState state = myStateMap.get(myKey); + if (state == null) return null; + state = state.copy().scaleDown(scale); + if (isVisible(state)) { + if (type == WindowState.class) { + return (T)state; + } + if (type == Point.class) { + return (T)state.getLocation(); + } + if (type == Dimension.class) { + return (T)state.getSize(); + } + if (type == Rectangle.class) { + return (T)state.getBounds(); + } + } + return null; + }; + + T state = getState.apply(getKey(screen, key)); if (state == null) { - state = get(getOldKey(screen, key), type); + state = getState.apply(getOldKey(screen, key)); } if (state != null) { return state; @@ -229,29 +254,7 @@ abstract class WindowStateServiceImpl extends WindowStateService implements Pers if (object != null) { return getFor(null, key, type); } - return get(new KeyPair(key, 1f), type); - } - - @SuppressWarnings("unchecked") - private T get(@NotNull KeyPair keyPair, @NotNull Class type) { - WindowState state = myStateMap.get(keyPair.first); - if (state == null) return null; - state = state.copy().scaleDown(keyPair.second); - if (isVisible(state)) { - if (type == WindowState.class) { - return (T)state; - } - if (type == Point.class) { - return (T)state.getLocation(); - } - if (type == Dimension.class) { - return (T)state.getSize(); - } - if (type == Rectangle.class) { - return (T)state.getBounds(); - } - } - return null; + return getState.apply(key); } private void putFor(Object object, @NotNull String key, @@ -261,63 +264,64 @@ abstract class WindowStateServiceImpl extends WindowStateService implements Pers boolean fullScreen, boolean fullScreenSet) { synchronized (myStateMap) { GraphicsDevice screen = getScreen(object); - KeyPair oldKeyPair = getOldKey(screen, key); + float scale = getSysScale(screen); - putImpl(getKey(screen, key), oldKeyPair, location, locationSet, size, sizeSet, maximized, maximizedSet, fullScreen, fullScreenSet); + BiFunction putState = (myNewKey, myOldKey) -> { + // remove & migrate the old key state + WindowState oldState = myOldKey != null ? myStateMap.remove(myOldKey) : null; + if (oldState != null) { + oldState.scaleDown(scale); + WindowState newState = myStateMap.get(myNewKey); + if (newState != null) { + newState.merge(oldState); + } else { + myStateMap.put(myNewKey, oldState); + } + } + // put the new key state + WindowState state = myStateMap.get(myNewKey); + if (state != null) { + if (state.set(location, locationSet, size, sizeSet, maximized, maximizedSet, fullScreen, fullScreenSet)) { + state.scaleUp(scale); + } else { + myStateMap.remove(myNewKey); + } + } + else { + state = new WindowState(); + if (state.set(location, locationSet, size, sizeSet, maximized, maximizedSet, fullScreen, fullScreenSet)) { + state.scaleUp(scale); + myStateMap.put(myNewKey, state); + } + } + return null; + }; + + putState.apply(getKey(screen, key), getOldKey(screen, key)); if (screen != null) { - putImpl(getKey(null, key), oldKeyPair, location, locationSet, size, sizeSet, maximized, maximizedSet, fullScreen, fullScreenSet); + putState.apply(getKey(null, key), getOldKey(null, key)); } - putImpl(new KeyPair(key, 1f), oldKeyPair, location, locationSet, size, sizeSet, maximized, maximizedSet, fullScreen, fullScreenSet); + putState.apply(key, null); } } - private void putImpl(@NotNull KeyPair keyPair, - @NotNull KeyPair oldKeyPair, - Point location, boolean locationSet, - Dimension size, boolean sizeSet, - boolean maximized, boolean maximizedSet, - boolean fullScreen, boolean fullScreenSet) { - WindowState state = myStateMap.get(keyPair.first); - - // may be convert the old key state to the new key - WindowState oldState = myStateMap.remove(oldKeyPair.first); - if (oldState != null) { - oldState.scaleDown(oldKeyPair.second); - if (state != null) { - state.merge(oldState); - } else { - myStateMap.put(keyPair.first, state = oldState); - } - } - if (state != null) { - if (state.set(location, locationSet, size, sizeSet, maximized, maximizedSet, fullScreen, fullScreenSet)) { - state.scaleUp(keyPair.second); - } else { - myStateMap.remove(keyPair.first); - } - } - else { - state = new WindowState(); - if (state.set(location, locationSet, size, sizeSet, maximized, maximizedSet, fullScreen, fullScreenSet)) { - state.scaleUp(keyPair.second); - myStateMap.put(keyPair.first, state); - } - } + private static float getSysScale(GraphicsDevice screen) { + return UIUtil.isJreHiDPIEnabled() && screen != null ? JBUI.sysScale(screen.getDefaultConfiguration()) : 1f; } /* * todo: old hidpi-unaware key; to be removed */ @NotNull - private static KeyPair getOldKey(GraphicsDevice screen, String key) { + private static String getOldKey(@Nullable GraphicsDevice screen, String key) { GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment(); if (environment.isHeadlessInstance()) { - return new KeyPair(key + ".headless", 1f); + return key + ".headless"; } StringBuilder sb = new StringBuilder(key); - float scale = 1f; for (GraphicsDevice device : environment.getScreenDevices()) { Rectangle bounds = device.getDefaultConfiguration().getBounds(); + normalizeSize(device, bounds); sb.append('/').append(bounds.x); sb.append('.').append(bounds.y); sb.append('.').append(bounds.width); @@ -325,25 +329,22 @@ abstract class WindowStateServiceImpl extends WindowStateService implements Pers } if (screen != null) { Rectangle bounds = screen.getDefaultConfiguration().getBounds(); + normalizeSize(screen, bounds); sb.append('@').append(bounds.x); sb.append('.').append(bounds.y); sb.append('.').append(bounds.width); sb.append('.').append(bounds.height); - if (UIUtil.isJreHiDPIEnabled()) { - scale = JBUI.sysScale(screen.getDefaultConfiguration()); - } } - return new KeyPair(sb.toString(), scale); + return sb.toString(); } @NotNull - private static KeyPair getKey(GraphicsDevice screen, String key) { + private static String getKey(@Nullable GraphicsDevice screen, String key) { GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment(); if (environment.isHeadlessInstance()) { - return new KeyPair(key + ".headless", 1f); + return key + ".headless"; } StringBuilder sb = new StringBuilder(key); - float scale = 1f; // not storing screen x,y due to relying on isVisible(state) on key retrieval if (screen == null) { for (GraphicsDevice device : environment.getScreenDevices()) { @@ -360,13 +361,10 @@ abstract class WindowStateServiceImpl extends WindowStateService implements Pers sb.append('.').append(bounds.height); if (JBUI.isPixHiDPI(screen.getDefaultConfiguration())) { int dpi = ((int)(96 * JBUI.pixScale(screen.getDefaultConfiguration()))); - sb.append("@" + dpi + "dpi"); - } - if (UIUtil.isJreHiDPIEnabled()) { - scale = JBUI.sysScale(screen.getDefaultConfiguration()); + sb.append("@").append(dpi).append("dpi"); } } - return new KeyPair(sb.toString(), scale); + return sb.toString(); } private static void normalizeSize(GraphicsDevice screen, Rectangle bounds) { @@ -390,15 +388,13 @@ abstract class WindowStateServiceImpl extends WindowStateService implements Pers } if (object instanceof Window) { Window window = (Window)object; - object = ScreenUtil.getScreenDevice(window.getBounds()); - if (object == null) { - LOG.warn("cannot find a screen for " + window); - return null; + GraphicsConfiguration gc = window.getGraphicsConfiguration(); + GraphicsDevice device = gc != null ? + window.getGraphicsConfiguration().getDevice() : ScreenUtil.getScreenDevice(window.getBounds()); + if (device != null) { + return device; } } - if (object instanceof GraphicsDevice) { - return (GraphicsDevice)object; - } LOG.warn("cannot find a screen for " + object); return null; } @@ -492,10 +488,4 @@ abstract class WindowStateServiceImpl extends WindowStateService implements Pers } return ScreenUtil.isVisible(new Rectangle(location, size)); } - - private static class KeyPair extends Pair { - public KeyPair(String key, Float scale) { - super(key, scale); - } - } } diff --git a/platform/platform-impl/src/com/intellij/ui/FontComboBox.java b/platform/platform-impl/src/com/intellij/ui/FontComboBox.java index 93c4d48fd0ba..9d82923d1f55 100644 --- a/platform/platform-impl/src/com/intellij/ui/FontComboBox.java +++ b/platform/platform-impl/src/com/intellij/ui/FontComboBox.java @@ -16,9 +16,11 @@ package com.intellij.ui; import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationBundle; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.ui.ComboBox; import com.intellij.util.ui.FontInfo; +import org.jetbrains.annotations.Nullable; import java.awt.Dimension; import java.util.ArrayList; @@ -40,11 +42,11 @@ public final class FontComboBox extends ComboBox { } public FontComboBox(boolean withAllStyles) { - this(withAllStyles, true); + this(withAllStyles, true, false); } - public FontComboBox(boolean withAllStyles, boolean filterNonLatin) { - super(new Model(withAllStyles, filterNonLatin)); + public FontComboBox(boolean withAllStyles, boolean filterNonLatin, boolean noFontItem) { + super(new Model(withAllStyles, filterNonLatin, noFontItem)); Dimension size = getPreferredSize(); size.width = size.height * 8; setPreferredSize(size); @@ -68,10 +70,14 @@ public final class FontComboBox extends ComboBox { return item == null ? null : item.toString(); } - public void setFontName(String item) { + public void setFontName(@Nullable String item) { myModel.setSelectedItem(item); } + public boolean isNoFontSelected() { + return myModel.isNoFontSelected(); + } + @Override public void setModel(ComboBoxModel model) { if (model instanceof Model) { @@ -84,12 +90,14 @@ public final class FontComboBox extends ComboBox { } private static final class Model extends AbstractListModel implements ComboBoxModel { + private final NoFontItem NO_FONT_ITEM; private volatile List myAllFonts = Collections.emptyList(); private volatile List myMonoFonts = Collections.emptyList(); private boolean myMonospacedOnly; private Object mySelectedItem; - private Model(boolean withAllStyles, boolean filterNonLatin) { + private Model(boolean withAllStyles, boolean filterNonLatin, boolean noFontItem) { + NO_FONT_ITEM = noFontItem ? new NoFontItem() : null; Application application = ApplicationManager.getApplication(); if (application == null || application.isUnitTestMode()) { setFonts(FontInfo.getAll(withAllStyles), filterNonLatin); @@ -132,17 +140,22 @@ public final class FontComboBox extends ComboBox { } @Override - public void setSelectedItem(Object item) { - if (item instanceof FontInfo) { - FontInfo info = getInfo(item); - if (info == null) { - List list = myMonospacedOnly ? myMonoFonts : myAllFonts; - item = list.isEmpty() ? null : list.get(0); - } + public void setSelectedItem(@Nullable Object item) { + if (item == null && NO_FONT_ITEM != null) { + item = NO_FONT_ITEM; } - if (item instanceof String) { - FontInfo info = getInfo(item); - if (info != null) item = info; + else { + if (item instanceof FontInfo) { + FontInfo info = getInfo(item); + if (info == null) { + List list = myMonospacedOnly ? myMonoFonts : myAllFonts; + item = list.isEmpty() ? null : list.get(0); + } + } + if (item instanceof String) { + FontInfo info = getInfo(item); + if (info != null) item = info; + } } if (!(mySelectedItem == null ? item == null : mySelectedItem.equals(item))) { mySelectedItem = item; @@ -150,16 +163,28 @@ public final class FontComboBox extends ComboBox { } } + public boolean isNoFontSelected() { + return getSelectedItem() == NO_FONT_ITEM; + } + @Override public int getSize() { List list = myMonospacedOnly ? myMonoFonts : myAllFonts; - return mySelectedItem instanceof String ? 1 + list.size() : list.size(); + int size = list.size(); + if (mySelectedItem instanceof String) size ++; + if (NO_FONT_ITEM != null) size++; + return size; } @Override public Object getElementAt(int index) { + int i = index; + if (NO_FONT_ITEM != null) { + if (index == 0) return NO_FONT_ITEM; + i --; + } List list = myMonospacedOnly ? myMonoFonts : myAllFonts; - return 0 <= index && index < list.size() ? list.get(index) : mySelectedItem; + return 0 <= i && i < list.size() ? list.get(i) : mySelectedItem; } private FontInfo getInfo(Object item) { @@ -170,5 +195,12 @@ public final class FontComboBox extends ComboBox { } return null; } + + private final static class NoFontItem { + @Override + public String toString() { + return ApplicationBundle.message("settings.editor.font.none"); + } + } } } diff --git a/platform/platform-impl/src/com/intellij/util/PsiErrorElementUtil.java b/platform/platform-impl/src/com/intellij/util/PsiErrorElementUtil.java index 5e9bc6559751..818c2fbfd728 100644 --- a/platform/platform-impl/src/com/intellij/util/PsiErrorElementUtil.java +++ b/platform/platform-impl/src/com/intellij/util/PsiErrorElementUtil.java @@ -20,11 +20,7 @@ import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiErrorElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.SyntaxTraverser; -import com.intellij.psi.impl.PsiManagerEx; +import com.intellij.psi.*; import com.intellij.psi.util.CachedValue; import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.CachedValuesManager; @@ -39,9 +35,7 @@ public class PsiErrorElementUtil { public static boolean hasErrors(@NotNull Project project, @NotNull VirtualFile virtualFile) { return ReadAction.compute(() -> { if (project.isDisposed() || !virtualFile.isValid()) return false; - - PsiManagerEx psiManager = PsiManagerEx.getInstanceEx(project); - PsiFile psiFile = psiManager.getFileManager().findFile(virtualFile); + PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile); return psiFile != null && hasErrors(psiFile); }); } diff --git a/platform/platform-resources-en/src/messages/ApplicationBundle.properties b/platform/platform-resources-en/src/messages/ApplicationBundle.properties index 9668a52b2261..3b9f5d210601 100644 --- a/platform/platform-resources-en/src/messages/ApplicationBundle.properties +++ b/platform/platform-resources-en/src/messages/ApplicationBundle.properties @@ -542,10 +542,11 @@ error.a.scheme.with.this.name.already.exists.or.was.deleted.without.applying.the title.select.font=Select Font action.apply.editor.font.settings=Apply editor font settings checkbox.show.only.monospaced.fonts=Show only monospaced fonts -primary.font=Primary font: -secondary.font=Secondary font: +primary.font=Font: +secondary.font=Fallback font: use.ligatures=Enable font ligatures ligatures.jre.warning=The JRE you are running {0} with
is known to have performance issues
related to ligatures support.
Use JetBrains Runtime instead to enable ligatures. +settings.editor.font.none= editbox.enter.tag.name=Enter tag name: title.tag.name=Tag Name title.xml=XML @@ -574,7 +575,7 @@ title.scope.based=By Scope title.colors.and.fonts=Colors \\& Fonts progress.analysing.font=Analysing font: {0} group.editor.font=Editor Font -label.fallback.fonts.list.description=If primary font fails, IDE tries to use the secondary one +label.fallback.fonts.list.description=For symbols not supported by the main font quickdoc.tooltip.font.size.by.wheel=Slider or Ctrl+Wheel change font size label.font.size=Font size: label.font.name=Name: diff --git a/platform/util/src/com/intellij/icons/AllIcons.java b/platform/util/src/com/intellij/icons/AllIcons.java index 6a4a440e3110..ed75689aa016 100644 --- a/platform/util/src/com/intellij/icons/AllIcons.java +++ b/platform/util/src/com/intellij/icons/AllIcons.java @@ -1289,6 +1289,7 @@ public class AllIcons { public static final Icon Push = IconLoader.getIcon("/vcs/push.png"); // 16x16 public static final Icon Remove = IconLoader.getIcon("/vcs/remove.png"); // 16x16 public static final Icon ResetStrip = IconLoader.getIcon("/vcs/resetStrip.png"); // 16x16 + public static final Icon RestoreDefaultSize = IconLoader.getIcon("/vcs/restoreDefaultSize.png"); // 16x16 public static final Icon Shelve = IconLoader.getIcon("/vcs/Shelve.png"); // 16x16 public static final Icon ShelveSilent = IconLoader.getIcon("/vcs/shelveSilent.png"); // 16x16 public static final Icon ShowUnversionedFiles = IconLoader.getIcon("/vcs/ShowUnversionedFiles.png"); // 16x16 diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogFullDetailsIndex.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogFullDetailsIndex.java index 61470653fda3..0cf752a66693 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogFullDetailsIndex.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogFullDetailsIndex.java @@ -31,7 +31,6 @@ import com.intellij.vcs.log.util.PersistentUtil; import gnu.trove.TIntHashSet; import org.jetbrains.annotations.NotNull; -import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.Map; @@ -130,11 +129,6 @@ public class VcsLogFullDetailsIndex implements Disposable { if (myDisposed) throw new ProcessCanceledException(); } - @NotNull - public static File getStorageFile(@NotNull String kind, @NotNull String id) { - return PersistentUtil.getStorageFile(INDEX, kind, id, getVersion(), false); - } - private class MyMapReduceIndex extends MapReduceIndex { public MyMapReduceIndex(@NotNull DataIndexer indexer, @NotNull DataExternalizer externalizer, @@ -157,7 +151,7 @@ public class VcsLogFullDetailsIndex implements Disposable { private static class MyMapIndexStorage extends MapIndexStorage { public MyMapIndexStorage(@NotNull String name, @NotNull String logId, @NotNull DataExternalizer externalizer) throws IOException { - super(VcsLogFullDetailsIndex.getStorageFile(name, logId), EnumeratorIntegerDescriptor.INSTANCE, externalizer, 5000, false); + super(PersistentUtil.getStorageFile(INDEX, name, logId, getVersion()), EnumeratorIntegerDescriptor.INSTANCE, externalizer, 5000, false); } @Override diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPathsIndex.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPathsIndex.java index 899713a03e5b..9347f8fa478b 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPathsIndex.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPathsIndex.java @@ -71,7 +71,7 @@ public class VcsLogPathsIndex extends VcsLogFullDetailsIndex createPathsEnumerator(@NotNull String logId) throws IOException { - File storageFile = PersistentUtil.getStorageFile(INDEX, INDEX_PATHS_IDS, logId, getVersion(), true); + File storageFile = PersistentUtil.getStorageFile(INDEX, INDEX_PATHS_IDS, logId, getVersion()); return new PersistentBTreeEnumerator<>(storageFile, SystemInfo.isFileSystemCaseSensitive ? EnumeratorStringDescriptor.INSTANCE : new ToLowerCaseStringDescriptor(), Page.PAGE_SIZE, null, getVersion()); @@ -309,7 +309,8 @@ public class VcsLogPathsIndex extends VcsLogFullDetailsIndex beforeData = fillDataWithNulls(result, parent, beforeId); beforeData.add(new ChangeData(ChangeKind.RENAMED_FROM, afterId)); diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPersistentIndex.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPersistentIndex.java index f9a4eeeb5874..d74819c3b7e1 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPersistentIndex.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/index/VcsLogPersistentIndex.java @@ -415,11 +415,11 @@ public class VcsLogPersistentIndex implements VcsLogIndex, Disposable { try { int version = getVersion(); - File commitsStorage = getStorageFile(INDEX, COMMITS, logId, version, true); + File commitsStorage = getStorageFile(INDEX, COMMITS, logId, version); commits = new PersistentSetImpl<>(commitsStorage, EnumeratorIntegerDescriptor.INSTANCE, Page.PAGE_SIZE, null, version); Disposer.register(disposable, () -> catchAndWarn(commits::close)); - File messagesStorage = getStorageFile(INDEX, MESSAGES, logId, VcsLogStorageImpl.VERSION + MESSAGES_VERSION, true); + File messagesStorage = getStorageFile(INDEX, MESSAGES, logId, VcsLogStorageImpl.VERSION + MESSAGES_VERSION); messages = new PersistentHashMap<>(messagesStorage, new IntInlineKeyDescriptor(), EnumeratorStringDescriptor.INSTANCE, Page.PAGE_SIZE); Disposer.register(disposable, () -> catchAndWarn(messages::close)); @@ -428,9 +428,9 @@ public class VcsLogPersistentIndex implements VcsLogIndex, Disposable { users = new VcsLogUserIndex(logId, userRegistry, fatalErrorHandler, disposable); paths = new VcsLogPathsIndex(logId, roots, fatalErrorHandler, disposable); - File parentsStorage = getStorageFile(INDEX, PARENTS, logId, getVersion(), true); + File parentsStorage = getStorageFile(INDEX, PARENTS, logId, version); parents = new PersistentHashMap<>(parentsStorage, EnumeratorIntegerDescriptor.INSTANCE, - new IntListDataExternalizer(), Page.PAGE_SIZE, getVersion()); + new IntListDataExternalizer(), Page.PAGE_SIZE, version); Disposer.register(disposable, () -> catchAndWarn(parents::close)); } catch (Throwable t) { diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/util/PersistentUtil.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/util/PersistentUtil.java index e81891dd7d9f..8f6da02b8bf7 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/util/PersistentUtil.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/util/PersistentUtil.java @@ -110,17 +110,23 @@ public class PersistentUtil { public static File getStorageFile(@NotNull String subdirName, @NotNull String kind, @NotNull String id, - int version, - boolean cleanupOldVersions) { + int version) { File subdir = new File(LOG_CACHE, subdirName); String safeLogId = PathUtilRt.suggestFileName(id, true, true); - File file = new File(subdir, safeLogId + "." + kind + "." + version); - if (cleanupOldVersions && !file.exists()) { - IOUtil.deleteAllFilesStartingWith(new File(subdir, safeLogId + "." + kind)); + File file = getFileName(kind, subdir, safeLogId, version); + if (!file.exists()) { + for (int oldVersion = 0; oldVersion < version; oldVersion++) { + IOUtil.deleteAllFilesStartingWith(getFileName(kind, subdir, safeLogId, oldVersion)); + } } return file; } + @NotNull + private static File getFileName(@NotNull String kind, @NotNull File subdir, @NotNull String safeLogId, int version) { + return new File(subdir, safeLogId + "." + kind + "." + version); + } + @NotNull public static File getCorruptionMarkerFile() { return new File(LOG_CACHE, CORRUPTION_MARKER); diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/methodmetrics/ParametersPerMethodInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/methodmetrics/ParametersPerMethodInspection.java index b760b1ec3936..ea717056a154 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/methodmetrics/ParametersPerMethodInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/methodmetrics/ParametersPerMethodInspection.java @@ -19,7 +19,6 @@ import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiParameterList; import com.siyeh.InspectionGadgetsBundle; import com.siyeh.ig.BaseInspectionVisitor; -import com.siyeh.ig.psiutils.LibraryUtil; import org.jetbrains.annotations.NotNull; public class ParametersPerMethodInspection extends MethodMetricInspection { @@ -76,7 +75,8 @@ public class ParametersPerMethodInspection extends MethodMetricInspection { if (parametersCount <= getLimit()) { return; } - if (LibraryUtil.isOverrideOfLibraryMethod(method)) { + //skip all derivatives + if (method.findDeepestSuperMethods().length > 0) { return; } registerMethodError(method, Integer.valueOf(parametersCount)); diff --git a/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/InjectionsSettingsUI.java b/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/InjectionsSettingsUI.java index 40649feaf573..9c2e48b07843 100644 --- a/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/InjectionsSettingsUI.java +++ b/plugins/IntelliLang/src/org/intellij/plugins/intelliLang/InjectionsSettingsUI.java @@ -709,7 +709,7 @@ public class InjectionsSettingsUI extends SearchableConfigurable.Parent.Abstract } private void doImportAction(final DataContext dataContext) { - final FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, true, false, true, false) { + final FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, false, false, true, false) { @Override public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) { return super.isFileVisible(file, showHiddenFiles) && diff --git a/plugins/IntentionPowerPak/src/com/siyeh/ipp/concatenation/SimpleStringConcatenationPredicate.java b/plugins/IntentionPowerPak/src/com/siyeh/ipp/concatenation/SimpleStringConcatenationPredicate.java index 1b5e41ed0fb7..8393cbd955bf 100644 --- a/plugins/IntentionPowerPak/src/com/siyeh/ipp/concatenation/SimpleStringConcatenationPredicate.java +++ b/plugins/IntentionPowerPak/src/com/siyeh/ipp/concatenation/SimpleStringConcatenationPredicate.java @@ -16,7 +16,9 @@ package com.siyeh.ipp.concatenation; import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.psi.PsiAnnotationMethod; import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; import com.siyeh.ig.psiutils.ExpressionUtils; import com.siyeh.ipp.base.PsiElementPredicate; @@ -33,6 +35,7 @@ class SimpleStringConcatenationPredicate implements PsiElementPredicate { if (!ExpressionUtils.isConcatenation(element)) { return false; } - return !(excludeConcatenationsInsideAnnotations && AnnotationUtil.isInsideAnnotation(element)); + return !(excludeConcatenationsInsideAnnotations && (AnnotationUtil.isInsideAnnotation(element) || + PsiTreeUtil.getParentOfType(element, PsiAnnotationMethod.class) != null)); } } diff --git a/plugins/IntentionPowerPak/src/com/siyeh/ipp/functional/ExtractToMethodReferenceIntention.java b/plugins/IntentionPowerPak/src/com/siyeh/ipp/functional/ExtractToMethodReferenceIntention.java index 065bfd5899af..294f6f5074e9 100644 --- a/plugins/IntentionPowerPak/src/com/siyeh/ipp/functional/ExtractToMethodReferenceIntention.java +++ b/plugins/IntentionPowerPak/src/com/siyeh/ipp/functional/ExtractToMethodReferenceIntention.java @@ -130,7 +130,7 @@ public class ExtractToMethodReferenceIntention extends BaseElementAtCaretIntenti targetMethodName + parameters, targetClass); PsiCodeBlock targetMethodBody = emptyMethod.getBody(); LOG.assertTrue(targetMethodBody != null); - targetMethodBody.addRange(elements[0], elements[elements.length - 1]); + if (elements.length > 0) targetMethodBody.addRange(elements[0], elements[elements.length - 1]); PsiMethod method = (PsiMethod)CodeStyleManager.getInstance(project).reformat(JavaCodeStyleManager.getInstance(project).shortenClassReferences(targetClass.add(emptyMethod))); PsiMethodReferenceExpression methodReference = diff --git a/plugins/IntentionPowerPak/src/com/siyeh/ipp/initialization/SplitDeclarationAndInitializationIntention.java b/plugins/IntentionPowerPak/src/com/siyeh/ipp/initialization/SplitDeclarationAndInitializationIntention.java index aebb8f568814..c7f1a29a45aa 100644 --- a/plugins/IntentionPowerPak/src/com/siyeh/ipp/initialization/SplitDeclarationAndInitializationIntention.java +++ b/plugins/IntentionPowerPak/src/com/siyeh/ipp/initialization/SplitDeclarationAndInitializationIntention.java @@ -16,8 +16,11 @@ package com.siyeh.ipp.initialization; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.search.LocalSearchScope; +import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.refactoring.util.RefactoringUtil; import com.siyeh.IntentionPowerPackBundle; import com.siyeh.ipp.base.Intention; @@ -26,6 +29,8 @@ import com.siyeh.ipp.psiutils.HighlightUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import java.util.function.Predicate; + public class SplitDeclarationAndInitializationIntention extends Intention { @Override @@ -46,58 +51,55 @@ public class SplitDeclarationAndInitializationIntention extends Intention { if (containingClass == null) { return; } - final boolean fieldIsStatic = - field.hasModifierProperty(PsiModifier.STATIC); - final PsiClassInitializer[] classInitializers = - containingClass.getInitializers(); + final boolean fieldIsStatic = field.hasModifierProperty(PsiModifier.STATIC); + final PsiClassInitializer[] classInitializers = containingClass.getInitializers(); PsiClassInitializer classInitializer = null; final int fieldOffset = field.getTextOffset(); for (PsiClassInitializer existingClassInitializer : classInitializers) { - final int initializerOffset = - existingClassInitializer.getTextOffset(); + final int initializerOffset = existingClassInitializer.getTextOffset(); if (initializerOffset <= fieldOffset) { continue; } - final boolean initializerIsStatic = - existingClassInitializer.hasModifierProperty( - PsiModifier.STATIC); + final boolean initializerIsStatic = existingClassInitializer.hasModifierProperty(PsiModifier.STATIC); if (initializerIsStatic == fieldIsStatic) { - classInitializer = existingClassInitializer; - break; + Predicate usedBeforeInitializer = ref -> { + PsiElement refElement = ref.getElement(); + if (refElement == null) { + return true; + } + TextRange textRange = refElement.getTextRange(); + return textRange == null || textRange.getStartOffset() < initializerOffset; + }; + if (ReferencesSearch.search(field, new LocalSearchScope(containingClass)).findAll().stream().noneMatch(usedBeforeInitializer)) { + classInitializer = existingClassInitializer; + break; + } } } final PsiManager manager = field.getManager(); final Project project = manager.getProject(); - final PsiElementFactory elementFactory = - JavaPsiFacade.getInstance(project).getElementFactory(); + final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(project).getElementFactory(); if (classInitializer == null) { classInitializer = elementFactory.createClassInitializer(); - classInitializer = (PsiClassInitializer) - containingClass.addAfter(classInitializer, field); + classInitializer = (PsiClassInitializer)containingClass.addAfter(classInitializer, field); // add some whitespace between the field and the class initializer - final PsiElement whitespace = - PsiParserFacade.SERVICE.getInstance(project).createWhiteSpaceFromText("\n"); + final PsiElement whitespace = PsiParserFacade.SERVICE.getInstance(project).createWhiteSpaceFromText("\n"); containingClass.addAfter(whitespace, field); } final PsiCodeBlock body = classInitializer.getBody(); - @NonNls final String initializationStatementText = - field.getName() + " = " + initializerText + ';'; - final PsiExpressionStatement statement = - (PsiExpressionStatement)elementFactory.createStatementFromText( - initializationStatementText, body); + @NonNls final String initializationStatementText = field.getName() + " = " + initializerText + ';'; + final PsiExpressionStatement statement = (PsiExpressionStatement)elementFactory.createStatementFromText(initializationStatementText, body); final PsiElement addedElement = body.add(statement); if (fieldIsStatic) { - final PsiModifierList modifierList = - classInitializer.getModifierList(); + final PsiModifierList modifierList = classInitializer.getModifierList(); if (modifierList != null) { modifierList.setModifierProperty(PsiModifier.STATIC, true); } } initializer.delete(); CodeStyleManager.getInstance(manager.getProject()).reformat(classInitializer); - HighlightUtil.highlightElement(addedElement, - IntentionPowerPackBundle.message( + HighlightUtil.highlightElement(addedElement, IntentionPowerPackBundle.message( "press.escape.to.remove.highlighting.message")); } } \ No newline at end of file diff --git a/plugins/IntentionPowerPak/src/com/siyeh/ipp/varargs/VarargArgumentsPredicate.java b/plugins/IntentionPowerPak/src/com/siyeh/ipp/varargs/VarargArgumentsPredicate.java index bc9fc916519b..d2fb11276c48 100644 --- a/plugins/IntentionPowerPak/src/com/siyeh/ipp/varargs/VarargArgumentsPredicate.java +++ b/plugins/IntentionPowerPak/src/com/siyeh/ipp/varargs/VarargArgumentsPredicate.java @@ -15,7 +15,9 @@ */ package com.siyeh.ipp.varargs; +import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil; import com.intellij.psi.*; +import com.intellij.psi.util.PsiTypesUtil; import com.intellij.psi.util.PsiUtil; import com.siyeh.ipp.base.PsiElementPredicate; import org.jetbrains.annotations.NotNull; @@ -48,29 +50,22 @@ class VarargArgumentsPredicate implements PsiElementPredicate { // "Unnecessarily qualified static usage" inspection // the psi gets into a bad state, this guards against that. // http://www.jetbrains.net/jira/browse/IDEADEV-40124 - final PsiReferenceExpression methodExpression = - methodCallExpression.getMethodExpression(); - final PsiExpression qualifier = - methodExpression.getQualifierExpression(); + final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression(); + final PsiExpression qualifier = methodExpression.getQualifierExpression(); if (qualifier == null) { - final PsiReferenceParameterList typeParameterList = - methodExpression.getParameterList(); + final PsiReferenceParameterList typeParameterList = methodExpression.getParameterList(); if (typeParameterList != null) { - final PsiTypeElement[] typeParameterElements = - typeParameterList.getTypeParameterElements(); + final PsiTypeElement[] typeParameterElements = typeParameterList.getTypeParameterElements(); if (typeParameterElements.length > 0) { return false; } } } - final PsiParameter[] parameters = parameterList.getParameters(); - final PsiParameter lastParameter = parameters[parameters.length - 1]; - final PsiEllipsisType lastParameterType = (PsiEllipsisType)lastParameter.getType(); - final PsiType lastType = lastParameterType.getComponentType(); final JavaResolveResult resolveResult = methodCallExpression.resolveMethodGenerics(); final PsiSubstitutor substitutor = resolveResult.getSubstitutor(); - final PsiType substitutedType = substitutor.substitute(lastType); + PsiType lastParameterType = PsiTypesUtil.getParameterType(parameterList.getParameters(), parametersCount - 1, true); + final PsiType substitutedType = substitutor.substitute(lastParameterType); if (substitutedType instanceof PsiCapturedWildcardType) { final PsiCapturedWildcardType capturedWildcardType = (PsiCapturedWildcardType)substitutedType; if (!capturedWildcardType.getWildcard().isSuper()) { @@ -78,13 +73,15 @@ class VarargArgumentsPredicate implements PsiElementPredicate { return false; } } + + if (!JavaGenericsUtil.isReifiableType(substitutedType)) { + return false; + } if (arguments.length != parametersCount) { return true; } - final PsiExpression lastExpression = - arguments[arguments.length - 1]; - final PsiExpression expression = PsiUtil.deparenthesizeExpression( - lastExpression); + final PsiExpression lastExpression = arguments[arguments.length - 1]; + final PsiExpression expression = PsiUtil.deparenthesizeExpression(lastExpression); if (expression instanceof PsiLiteralExpression) { final String text = expression.getText(); if ("null".equals(text)) { diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/concatenation/string_builder/ConstantRequiredInsideAnnotationMethod.java b/plugins/IntentionPowerPak/test/com/siyeh/ipp/concatenation/string_builder/ConstantRequiredInsideAnnotationMethod.java new file mode 100644 index 000000000000..c69e724452dd --- /dev/null +++ b/plugins/IntentionPowerPak/test/com/siyeh/ipp/concatenation/string_builder/ConstantRequiredInsideAnnotationMethod.java @@ -0,0 +1,5 @@ +package com.siyeh.ipp.concatenation.string_builder; + +public @interface ConstantRequiredInsideAnnotationMethod { + String val() default "hey," + ""; +} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/EmptyCodeBlock.java b/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/EmptyCodeBlock.java new file mode 100644 index 000000000000..bf1787f705eb --- /dev/null +++ b/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/EmptyCodeBlock.java @@ -0,0 +1,5 @@ +class B { + { + Runnable r = () -> {} ; + } +} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/EmptyCodeBlock_after.java b/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/EmptyCodeBlock_after.java new file mode 100644 index 000000000000..8ab95092e18a --- /dev/null +++ b/plugins/IntentionPowerPak/test/com/siyeh/ipp/functional/extractToMethodReference/EmptyCodeBlock_after.java @@ -0,0 +1,8 @@ +class B { + { + Runnable r = B::run; + } + + private static void run() { + } +} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/initialization/FieldUsedBeforeInitializer.java b/plugins/IntentionPowerPak/test/com/siyeh/ipp/initialization/FieldUsedBeforeInitializer.java new file mode 100644 index 000000000000..594414f839bb --- /dev/null +++ b/plugins/IntentionPowerPak/test/com/siyeh/ipp/initialization/FieldUsedBeforeInitializer.java @@ -0,0 +1,8 @@ +class Foo { + static final int f1 = 2; + static final int f2 = f1 + 1; + + static { + System.out.println(); + } +} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/test/com/siyeh/ipp/initialization/FieldUsedBeforeInitializer_after.java b/plugins/IntentionPowerPak/test/com/siyeh/ipp/initialization/FieldUsedBeforeInitializer_after.java new file mode 100644 index 000000000000..1697906d1cb0 --- /dev/null +++ b/plugins/IntentionPowerPak/test/com/siyeh/ipp/initialization/FieldUsedBeforeInitializer_after.java @@ -0,0 +1,13 @@ +class Foo { + static final int f1; + + static { + f1 = 2; + } + + static final int f2 = f1 + 1; + + static { + System.out.println(); + } +} \ No newline at end of file diff --git a/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/concatenation/ReplaceConcatenationWithStringBufferIntentionTest.java b/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/concatenation/ReplaceConcatenationWithStringBufferIntentionTest.java index 82ee9ca4b93e..3baa064bf8c8 100644 --- a/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/concatenation/ReplaceConcatenationWithStringBufferIntentionTest.java +++ b/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/concatenation/ReplaceConcatenationWithStringBufferIntentionTest.java @@ -10,6 +10,7 @@ public class ReplaceConcatenationWithStringBufferIntentionTest extends IPPTestCa public void testNonStringConcatenationStart() { doTest(); } public void testConcatenationInsideAppend() { doTest(); } + public void testConstantRequiredInsideAnnotationMethod() { assertIntentionNotAvailable(); } @Override protected String getIntentionName() { diff --git a/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/functional/ExtractToMethodReferenceTest.java b/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/functional/ExtractToMethodReferenceTest.java index 5be4b04af3bf..c594a70339ac 100644 --- a/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/functional/ExtractToMethodReferenceTest.java +++ b/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/functional/ExtractToMethodReferenceTest.java @@ -54,6 +54,10 @@ public class ExtractToMethodReferenceTest extends IPPTestCase { assertIntentionNotAvailable(); } + public void testEmptyCodeBlock() throws Exception { + doTest(); + } + public void testUsedLocalVariables() throws Exception { assertIntentionNotAvailable(); } diff --git a/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/initialization/SplitDeclarationAndInitializationIntentionTest.java b/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/initialization/SplitDeclarationAndInitializationIntentionTest.java index 933d5e2e7964..e8c5659abdd4 100644 --- a/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/initialization/SplitDeclarationAndInitializationIntentionTest.java +++ b/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/initialization/SplitDeclarationAndInitializationIntentionTest.java @@ -26,6 +26,7 @@ public class SplitDeclarationAndInitializationIntentionTest extends IPPTestCase public void testArrayInitializer() { doTest(); } public void testArray() { doTest(); } + public void testFieldUsedBeforeInitializer() { doTest(); } @Override protected String getRelativePath() { diff --git a/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/vararg/WrapVarargArgumentsWithExplicitArrayIntentionTest.java b/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/vararg/WrapVarargArgumentsWithExplicitArrayIntentionTest.java index fc355c2f8579..4f811c316cc4 100644 --- a/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/vararg/WrapVarargArgumentsWithExplicitArrayIntentionTest.java +++ b/plugins/IntentionPowerPak/testSrc/com/siyeh/ipp/vararg/WrapVarargArgumentsWithExplicitArrayIntentionTest.java @@ -58,4 +58,16 @@ public class WrapVarargArgumentsWithExplicitArrayIntentionTest extends IPPTestCa " }\n" + "}"); } + + public void testNonReifiable() { + doTestIntentionNotAvailable( + "" + + "import java.util.*;\n" + + "class Y {\n" + + " void m(Set... t){}\n" + + " public static void run(Set s) {\n" + + " m(/*_Wrap vararg arguments with explicit array creation*/s);\n" + + " }\n" + + "}"); + } } diff --git a/plugins/git4idea/src/git4idea/GitUtil.java b/plugins/git4idea/src/git4idea/GitUtil.java index 038ac6e059b4..a6d53798ac1a 100644 --- a/plugins/git4idea/src/git4idea/GitUtil.java +++ b/plugins/git4idea/src/git4idea/GitUtil.java @@ -368,8 +368,9 @@ public class GitUtil { public static VirtualFile getGitRootOrNull(@NotNull final FilePath filePath) { File root = filePath.getIOFile(); while (root != null) { - File gitDir = findGitDir(root); - if (gitDir != null) return LocalFileSystem.getInstance().findFileByIoFile(root); + if (isGitRoot(root)) { + return LocalFileSystem.getInstance().findFileByIoFile(root); + } root = root.getParentFile(); } return null; diff --git a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java index 30f02e80538d..7cdc4ee0930e 100644 --- a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java +++ b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopup.java @@ -23,6 +23,7 @@ import com.intellij.dvcs.ui.RootAction; import com.intellij.openapi.actionSystem.ActionGroup; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.actionSystem.EmptyAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.util.containers.ContainerUtil; @@ -34,7 +35,6 @@ import git4idea.repo.GitRepositoryManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.swing.*; import java.util.List; import java.util.Objects; @@ -43,6 +43,7 @@ import static com.intellij.dvcs.branch.DvcsBranchPopup.MyMoreIndex.MAX_NUM; import static com.intellij.dvcs.ui.BranchActionGroupPopup.wrapWithMoreActionIfNeeded; import static com.intellij.dvcs.ui.BranchActionUtil.FAVORITE_BRANCH_COMPARATOR; import static com.intellij.dvcs.ui.BranchActionUtil.getNumOfTopShownBranches; +import static com.intellij.util.ObjectUtils.tryCast; import static com.intellij.util.containers.ContainerUtil.map; import static java.util.stream.Collectors.toList; @@ -64,8 +65,8 @@ class GitBranchPopup extends DvcsBranchPopup { static GitBranchPopup getInstance(@NotNull final Project project, @NotNull GitRepository currentRepository) { final GitVcsSettings vcsSettings = GitVcsSettings.getInstance(project); Condition preselectActionCondition = action -> { - if (action instanceof GitBranchPopupActions.LocalBranchActions) { - GitBranchPopupActions.LocalBranchActions branchAction = (GitBranchPopupActions.LocalBranchActions)action; + GitBranchPopupActions.LocalBranchActions branchAction = getBranchAction(action); + if (branchAction != null) { String branchName = branchAction.getBranchName(); String recentBranch; @@ -86,6 +87,13 @@ class GitBranchPopup extends DvcsBranchPopup { return new GitBranchPopup(currentRepository, GitUtil.getRepositoryManager(project), vcsSettings, preselectActionCondition); } + @Nullable + private static GitBranchPopupActions.LocalBranchActions getBranchAction(@NotNull AnAction action) { + AnAction resultAction = + action instanceof EmptyAction.MyDelegatingActionGroup ? ((EmptyAction.MyDelegatingActionGroup)action).getDelegate() : action; + return tryCast(resultAction, GitBranchPopupActions.LocalBranchActions.class); + } + private GitBranchPopup(@NotNull GitRepository currentRepository, @NotNull GitRepositoryManager repositoryManager, @NotNull GitVcsSettings vcsSettings, @@ -94,24 +102,6 @@ class GitBranchPopup extends DvcsBranchPopup { preselectActionCondition, DIMENSION_SERVICE_KEY); } - @Override - protected void setCurrentBranchInfo() { - String currentBranchText = "Current branch"; - if (myRepositoryManager.moreThanOneRoot()) { - if (myMultiRootBranchConfig.diverged()) { - currentBranchText += " in " + DvcsUtil.getShortRepositoryName(myCurrentRepository) + ": " + - GitBranchUtil.getDisplayableBranchText(myCurrentRepository); - } - else { - currentBranchText += ": " + myMultiRootBranchConfig.getCurrentBranch(); - } - } - else { - currentBranchText += ": " + GitBranchUtil.getDisplayableBranchText(myCurrentRepository); - } - myPopup.setAdText(currentBranchText, SwingConstants.CENTER); - } - @Override protected void fillWithCommonRepositoryActions(@NotNull DefaultActionGroup popupGroup, @NotNull AbstractRepositoryManager repositoryManager) { @@ -122,11 +112,19 @@ class GitBranchPopup extends DvcsBranchPopup { popupGroup.addAll(createRepositoriesActions()); popupGroup.addSeparator("Common Local Branches"); - List localBranchActions = - myMultiRootBranchConfig.getLocalBranchNames().stream().map(l -> createLocalBranchActions(allRepositories, l)).filter(Objects::nonNull) - .collect(toList()); - wrapWithMoreActionIfNeeded(myProject, popupGroup, ContainerUtil.sorted(localBranchActions, FAVORITE_BRANCH_COMPARATOR), - getNumOfTopShownBranches(localBranchActions), SHOW_ALL_LOCALS_KEY, true); + List localBranchActions = myMultiRootBranchConfig.getLocalBranchNames().stream() + .map(l -> createLocalBranchActions(allRepositories, l)) + .filter(Objects::nonNull) + .sorted(FAVORITE_BRANCH_COMPARATOR) + .collect(toList()); + int topShownBranches = getNumOfTopShownBranches(localBranchActions); + String currentBranch = myMultiRootBranchConfig.getCurrentBranch(); + if (currentBranch != null) { + localBranchActions + .add(0, new GitBranchPopupActions.CurrentBranchActions(myProject, allRepositories, currentBranch, myCurrentRepository)); + topShownBranches++; + } + wrapWithMoreActionIfNeeded(myProject, popupGroup, localBranchActions, topShownBranches, SHOW_ALL_LOCALS_KEY, true); popupGroup.addSeparator("Common Remote Branches"); List remoteBranchActions = map(((GitMultiRootBranchConfig)myMultiRootBranchConfig).getRemoteBranches(), @@ -154,7 +152,7 @@ class GitBranchPopup extends DvcsBranchPopup { popupGroup.addSeparator("Repositories"); List rootActions = DvcsUtil.sortRepositories(myRepositoryManager.getRepositories()).stream() .map(repo -> new RootAction<>(repo, new GitBranchPopupActions(repo.getProject(), repo).createActions(), - GitBranchUtil.getDisplayableBranchText(repo))).collect(toList()); + isBranchesDiverged() ? GitBranchUtil.getDisplayableBranchText(repo) : null)).collect(toList()); wrapWithMoreActionIfNeeded(myProject, popupGroup, rootActions, rootActions.size() > MAX_NUM ? DEFAULT_NUM : MAX_NUM, SHOW_ALL_REPOSITORIES); return popupGroup; diff --git a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java index 3aff81024041..8012860e3ac3 100644 --- a/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java +++ b/plugins/git4idea/src/git4idea/ui/branch/GitBranchPopupActions.java @@ -27,6 +27,7 @@ import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.util.containers.ContainerUtil; +import git4idea.GitLocalBranch; import git4idea.branch.GitBranchUtil; import git4idea.branch.GitBrancher; import git4idea.branch.GitNewBranchOptions; @@ -74,15 +75,21 @@ class GitBranchPopupActions { } popupGroup.addSeparator("Local Branches" + repoInfo); - List localBranchActions = - myRepository.getBranches().getLocalBranches().stream() - .sorted() - .filter(branch -> !branch.equals(myRepository.getCurrentBranch())) - .map(branch -> new LocalBranchActions(myProject, repositoryList, branch.getName(), myRepository)) - .collect(toList()); + GitLocalBranch currentBranch = myRepository.getCurrentBranch(); + List localBranchActions = myRepository.getBranches().getLocalBranches().stream() + .sorted() + .filter(branch -> !branch.equals(currentBranch)) + .map(branch -> new LocalBranchActions(myProject, repositoryList, branch.getName(), myRepository)) + .sorted(FAVORITE_BRANCH_COMPARATOR) + .collect(toList()); + int topShownBranches = getNumOfTopShownBranches(localBranchActions); + if (currentBranch != null) { + localBranchActions.add(0, new CurrentBranchActions(myProject, repositoryList, currentBranch.getName(), myRepository)); + topShownBranches++; + } // if there are only a few local favorites -> show all; for remotes it's better to show only favorites; - wrapWithMoreActionIfNeeded(myProject, popupGroup, ContainerUtil.sorted(localBranchActions, FAVORITE_BRANCH_COMPARATOR), - getNumOfTopShownBranches(localBranchActions), firstLevelGroup ? GitBranchPopup.SHOW_ALL_LOCALS_KEY : null, + wrapWithMoreActionIfNeeded(myProject, popupGroup, localBranchActions, + topShownBranches, firstLevelGroup ? GitBranchPopup.SHOW_ALL_LOCALS_KEY : null, firstLevelGroup); popupGroup.addSeparator("Remote Branches" + repoInfo); @@ -160,9 +167,9 @@ class GitBranchPopupActions { */ static class LocalBranchActions extends BranchActionGroup implements PopupElementWithAdditionalInfo { - private final Project myProject; - private final List myRepositories; - private final String myBranchName; + protected final Project myProject; + protected final List myRepositories; + protected final String myBranchName; @NotNull private final GitRepository mySelectedRepository; private final GitBranchManager myGitBranchManager; @@ -310,6 +317,27 @@ class GitBranchPopupActions { } } + static class CurrentBranchActions extends LocalBranchActions { + CurrentBranchActions(@NotNull Project project, + @NotNull List repositories, + @NotNull String branchName, + @NotNull GitRepository selectedRepository) { + super(project, repositories, branchName, selectedRepository); + } + + @NotNull + @Override + public AnAction[] getChildren(@Nullable AnActionEvent e) { + return new AnAction[]{new LocalBranchActions.RenameBranchAction(myProject, myRepositories, myBranchName)}; + } + + @Nullable + @Override + public String getPrefixInfo() { + return "current"; + } + } + /** * Actions available for remote branches */ diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopup.java b/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopup.java index 67979119747b..482cf5c2d1f6 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopup.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopup.java @@ -26,7 +26,6 @@ import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Conditions; -import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.zmlx.hg4idea.HgProjectSettings; @@ -34,7 +33,6 @@ import org.zmlx.hg4idea.repo.HgRepository; import org.zmlx.hg4idea.repo.HgRepositoryManager; import org.zmlx.hg4idea.util.HgUtil; -import javax.swing.*; import java.util.List; import java.util.Objects; @@ -79,12 +77,6 @@ public class HgBranchPopup extends DvcsBranchPopup { super(currentRepository, repositoryManager, hgMultiRootBranchConfig, vcsSettings, preselectActionCondition, DIMENSION_SERVICE_KEY); } - protected void setCurrentBranchInfo() { - String branchText = "Current branch : "; - //always display heavy branch name for additional info // - myPopup.setAdText(branchText + myCurrentRepository.getCurrentBranch(), SwingConstants.CENTER); - } - @Override protected void fillWithCommonRepositoryActions(@NotNull DefaultActionGroup popupGroup, @NotNull AbstractRepositoryManager repositoryManager) { @@ -99,16 +91,26 @@ public class HgBranchPopup extends DvcsBranchPopup { List branchActions = myMultiRootBranchConfig.getLocalBranchNames().stream() .map(b -> createLocalBranchActions(allRepositories, b, false)) - .filter(Objects::nonNull).collect(toList()); - wrapWithMoreActionIfNeeded(myProject, popupGroup, ContainerUtil.sorted(branchActions, FAVORITE_BRANCH_COMPARATOR), - getNumOfTopShownBranches(branchActions), SHOW_ALL_BRANCHES_KEY, true); + .filter(Objects::nonNull).sorted(FAVORITE_BRANCH_COMPARATOR).collect(toList()); + int topShownBranches = getNumOfTopShownBranches(branchActions); + String commonBranch = myMultiRootBranchConfig.getCommonName(HgRepository::getCurrentBranch); + if (commonBranch != null) { + branchActions.add(0, new HgBranchPopupActions.CurrentBranch(myProject, allRepositories, commonBranch)); + topShownBranches++; + } + wrapWithMoreActionIfNeeded(myProject, popupGroup, branchActions, topShownBranches, SHOW_ALL_BRANCHES_KEY, true); popupGroup.addSeparator("Common Bookmarks"); List bookmarkActions = ((HgMultiRootBranchConfig)myMultiRootBranchConfig).getBookmarkNames().stream() .map(bm -> createLocalBranchActions(allRepositories, bm, true)) - .filter(Objects::nonNull).collect(toList()); - wrapWithMoreActionIfNeeded(myProject, popupGroup, ContainerUtil.sorted(bookmarkActions, FAVORITE_BRANCH_COMPARATOR), - getNumOfTopShownBranches(bookmarkActions), SHOW_ALL_BOOKMARKS_KEY, true); + .filter(Objects::nonNull).sorted(FAVORITE_BRANCH_COMPARATOR).collect(toList()); + int topShownBookmarks = getNumOfTopShownBranches(bookmarkActions); + String commonBookmark = myMultiRootBranchConfig.getCommonName(HgRepository::getCurrentBookmark); + if (commonBookmark != null) { + bookmarkActions.add(0, new HgBranchPopupActions.CurrentActiveBookmark(myProject, allRepositories, commonBookmark)); + topShownBookmarks++; + } + wrapWithMoreActionIfNeeded(myProject, popupGroup, bookmarkActions, topShownBookmarks, SHOW_ALL_BOOKMARKS_KEY, true); } @Nullable @@ -124,9 +126,9 @@ public class HgBranchPopup extends DvcsBranchPopup { protected DefaultActionGroup createRepositoriesActions() { DefaultActionGroup popupGroup = new DefaultActionGroup(null, false); popupGroup.addSeparator("Repositories"); - List rootActions = DvcsUtil.sortRepositories(myRepositoryManager.getRepositories()).stream() - .map(repo -> new RootAction<>(repo, new HgBranchPopupActions(repo.getProject(), repo).createActions(), - HgUtil.getDisplayableBranchOrBookmarkText(repo))).collect(toList()); + List rootActions = DvcsUtil.sortRepositories(myRepositoryManager.getRepositories()).stream().map( + repo -> new RootAction<>(repo, new HgBranchPopupActions(repo.getProject(), repo).createActions(), + isBranchesDiverged() ? HgUtil.getDisplayableBranchOrBookmarkText(repo) : null)).collect(toList()); wrapWithMoreActionIfNeeded(myProject, popupGroup, rootActions, rootActions.size() > MAX_NUM ? DEFAULT_NUM : MAX_NUM, SHOW_ALL_REPOSITORIES); return popupGroup; diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopupActions.java b/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopupActions.java index b80579503082..1adb7c3478e3 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopupActions.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/branch/HgBranchPopupActions.java @@ -18,6 +18,7 @@ package org.zmlx.hg4idea.branch; import com.intellij.dvcs.DvcsUtil; import com.intellij.dvcs.repo.Repository; import com.intellij.dvcs.ui.NewBranchAction; +import com.intellij.dvcs.ui.PopupElementWithAdditionalInfo; import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.ActionGroup; import com.intellij.openapi.actionSystem.AnAction; @@ -34,7 +35,6 @@ import com.intellij.openapi.vcs.VcsBundle; import com.intellij.openapi.vcs.changes.*; import com.intellij.openapi.vcs.changes.ui.CommitChangeListDialog; import com.intellij.util.ArrayUtil; -import com.intellij.util.PlatformIcons; import com.intellij.util.containers.ContainerUtil; import com.intellij.vcs.log.Hash; import com.intellij.vcs.log.impl.HashImpl; @@ -87,13 +87,19 @@ public class HgBranchPopupActions { } popupGroup.addSeparator("Bookmarks" + repoInfo); + String currentBookmark = myRepository.getCurrentBookmark(); List bookmarkActions = getSortedNamesWithoutHashes(myRepository.getBookmarks()).stream() + .filter(bm -> !bm.equals(currentBookmark)) .map(bm -> new BookmarkActions(myProject, Collections.singletonList(myRepository), bm)) + .sorted(FAVORITE_BRANCH_COMPARATOR) .collect(toList()); - // if there are only a few local favorites -> show all; for remotes it's better to show only favorites; - wrapWithMoreActionIfNeeded(myProject, popupGroup, ContainerUtil.sorted(bookmarkActions, FAVORITE_BRANCH_COMPARATOR), - getNumOfTopShownBranches(bookmarkActions), firstLevelGroup ? HgBranchPopup.SHOW_ALL_BOOKMARKS_KEY : null, - firstLevelGroup); + int topShownBookmarks = getNumOfTopShownBranches(bookmarkActions); + if (currentBookmark != null) { + bookmarkActions.add(0, new CurrentActiveBookmark(myProject, Collections.singletonList(myRepository), currentBookmark)); + topShownBookmarks++; + } + wrapWithMoreActionIfNeeded(myProject, popupGroup, bookmarkActions, topShownBookmarks, + firstLevelGroup ? HgBranchPopup.SHOW_ALL_BOOKMARKS_KEY : null, firstLevelGroup); //only opened branches have to be shown popupGroup.addSeparator("Branches" + repoInfo); @@ -102,10 +108,11 @@ public class HgBranchPopupActions { .sorted() .filter(b -> !b.equals(myRepository.getCurrentBranch())) .map(b -> new BranchActions(myProject, Collections.singletonList(myRepository), b)) + .sorted(FAVORITE_BRANCH_COMPARATOR) .collect(toList()); - wrapWithMoreActionIfNeeded(myProject, popupGroup, ContainerUtil.sorted(branchActions, FAVORITE_BRANCH_COMPARATOR), - getNumOfTopShownBranches(branchActions), firstLevelGroup ? HgBranchPopup.SHOW_ALL_BRANCHES_KEY : null, - firstLevelGroup); + branchActions.add(0, new CurrentBranch(myProject, Collections.singletonList(myRepository), myRepository.getCurrentBranch())); + wrapWithMoreActionIfNeeded(myProject, popupGroup, branchActions, getNumOfTopShownBranches(branchActions) + 1, + firstLevelGroup ? HgBranchPopup.SHOW_ALL_BRANCHES_KEY : null, firstLevelGroup); return popupGroup; } @@ -279,6 +286,24 @@ public class HgBranchPopupActions { super(project, repositories, branchName, HgBranchType.BRANCH); } } + + public static class CurrentBranch extends BranchActions implements PopupElementWithAdditionalInfo { + public CurrentBranch(@NotNull Project project, @NotNull List repositories, @NotNull String branchName) { + super(project, repositories, branchName); + } + + @NotNull + @Override + public AnAction[] getChildren(@Nullable AnActionEvent e) { + return AnAction.EMPTY_ARRAY; + } + + @Nullable + @Override + public String getPrefixInfo() { + return "current"; + } + } /** * Actions available for bookmarks. @@ -287,9 +312,6 @@ public class HgBranchPopupActions { BookmarkActions(@NotNull Project project, @NotNull List repositories, @NotNull String branchName) { super(project, repositories, branchName, HgBranchType.BOOKMARK); - if (myRepositories.size() == 1 && branchName.equals(myRepositories.get(0).getCurrentBookmark())) { - getTemplatePresentation().setIcon(PlatformIcons.CHECK_ICON); - } } @NotNull @@ -314,4 +336,23 @@ public class HgBranchPopupActions { } } } + + public static class CurrentActiveBookmark extends BookmarkActions implements PopupElementWithAdditionalInfo { + + public CurrentActiveBookmark(@NotNull Project project, @NotNull List repositories, @NotNull String branchName) { + super(project, repositories, branchName); + } + + @NotNull + @Override + public AnAction[] getChildren(@Nullable AnActionEvent e) { + return new AnAction[]{new BookmarkActions.DeleteBookmarkAction(myProject, myRepositories, myBranchName)}; + } + + @Nullable + @Override + public String getPrefixInfo() { + return "active"; + } + } } diff --git a/python/IntelliLang-python/src/resources/pyInjections.xml b/python/IntelliLang-python/src/resources/pyInjections.xml index 0c04cf7018d4..b9c1d06c4c7d 100644 --- a/python/IntelliLang-python/src/resources/pyInjections.xml +++ b/python/IntelliLang-python/src/resources/pyInjections.xml @@ -80,5 +80,11 @@ + + + + + + diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py index f3d9d030fd2d..2e896252409b 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_vars.py @@ -413,26 +413,14 @@ def change_attr_expression(thread_id, frame_id, attr, expression, dbg, value=SEN MAXIMUM_ARRAY_SIZE = 100 -def table_like_struct_to_xml(array, name, roffset, coffset, rows, cols, format): - _, type_name, _ = get_type(array) - if type_name == 'ndarray': - array, metaxml, r, c, f = array_to_meta_xml(array, name, format) - xml = metaxml - format = '%' + f - if rows == -1 and cols == -1: - rows = r - cols = c - xml += array_to_xml(array, roffset, coffset, rows, cols, format) - elif type_name == 'DataFrame': - xml = dataframe_to_xml(array, name, roffset, coffset, rows, cols, format) - else: - raise VariableError("Do not know how to convert type %s to table" % (type_name)) - return "%s" % xml +def array_to_xml(array, name, roffset, coffset, rows, cols, format): + array, xml, r, c, f = array_to_meta_xml(array, name, format) + format = '%' + f + if rows == -1 and cols == -1: + rows = r + cols = c - -def array_to_xml(array, roffset, coffset, rows, cols, format): - xml = "" rows = min(rows, MAXIMUM_ARRAY_SIZE) cols = min(cols, MAXIMUM_ARRAY_SIZE) @@ -453,26 +441,19 @@ def array_to_xml(array, roffset, coffset, rows, cols, format): array = array[roffset:] rows = min(rows, len(array)) - xml += "" % (rows, cols) - for row in range(rows): - xml += "" % to_string(row) - for col in range(cols): - value = array - if rows == 1 or cols == 1: - if rows == 1 and cols == 1: - value = array[0] - else: - if rows == 1: - dim = col - else: - dim = row - value = array[dim] - if "ndarray" in str(type(value)): - value = value[0] + def get_value(row, col): + value = array + if rows == 1 or cols == 1: + if rows == 1 and cols == 1: + value = array[0] else: - value = array[row][col] - value = format % value - xml += var_to_xml(value, '') + value = array[(col if rows == 1 else row)] + if "ndarray" in str(type(value)): + value = value[0] + else: + value = array[row][col] + return value + xml += array_data_to_xml(rows, cols, lambda r: (get_value(r, c) for c in range(cols))) return xml @@ -533,9 +514,7 @@ def array_to_meta_xml(array, name, format): bounds = (0, 0) if type in "biufc": bounds = (array.min(), array.max()) - xml = '' % \ - (slice, rows, cols, format, type, bounds[1], bounds[0]) - return array, xml, rows, cols, format + return array, slice_to_xml(slice, rows, cols, format, type, bounds), rows, cols, format def array_default_format(type): @@ -547,6 +526,10 @@ def array_default_format(type): return 's' +def get_label(label): + return str(label) if not isinstance(label, tuple) else '/'.join(map(str, label)) + + def dataframe_to_xml(df, name, roffset, coffset, rows, cols, format): """ :type df: pandas.core.frame.DataFrame @@ -559,60 +542,80 @@ def dataframe_to_xml(df, name, roffset, coffset, rows, cols, format): """ + dim = len(df.axes) num_rows = df.shape[0] - num_cols = df.shape[1] - if (num_rows, num_cols) != df.shape: - df = df.iloc[0:num_rows, 0: num_cols] - slice = '.iloc[0:%s, 0:%s]' % (num_rows, num_cols) - else: - slice = '' - slice = name + slice - xml = '\n' % \ - (slice, num_rows, num_cols) + num_cols = df.shape[1] if dim > 1 else 1 + xml = slice_to_xml(name, num_rows, num_cols, "", "", (0, 0)) if (rows, cols) == (-1, -1): rows, cols = num_rows, num_cols rows = min(rows, MAXIMUM_ARRAY_SIZE) - cols = min(min(cols, MAXIMUM_ARRAY_SIZE), num_cols) + cols = min(cols, MAXIMUM_ARRAY_SIZE, num_cols) # need to precompute column bounds here before slicing! col_bounds = [None] * cols - for col in range(cols): - dtype = df.dtypes.iloc[coffset + col].kind - if dtype in "biufc": - cvalues = df.iloc[:, coffset + col] - bounds = (cvalues.min(), cvalues.max()) - else: - bounds = (0, 0) - col_bounds[col] = bounds - - df = df.iloc[roffset: roffset + rows, coffset: coffset + cols] - rows, cols = df.shape - - - xml += "\n" % (rows, cols) - format = format.replace('%', '') - col_formats = [] - - get_label = lambda label: str(label) if not isinstance(label, tuple) else '/'.join(map(str, label)) - - for col in range(cols): - dtype = df.dtypes.iloc[col].kind - fmt = format if (dtype == 'f' and format) else array_default_format(dtype) - col_formats.append('%' + fmt) - bounds = col_bounds[col] - - xml += '\n' % \ - (str(col), get_label(df.axes[1].values[col]), dtype, fmt, bounds[1], bounds[0]) - for row, label in enumerate(iter(df.axes[0])): - xml += "\n" % \ - (str(row), get_label(label)) - xml += "\n" - xml += "\n" % (rows, cols) - for row in range(rows): - xml += "\n" % str(row) + dtypes = [None] * cols + if dim > 1: for col in range(cols): - value = df.iat[row, col] - value = col_formats[col] % value + dtype = df.dtypes.iloc[coffset + col].kind + dtypes[col] = dtype + if dtype in "biufc": + cvalues = df.iloc[:, coffset + col] + bounds = (cvalues.min(), cvalues.max()) + else: + bounds = (0, 0) + col_bounds[col] = bounds + else: + dtype = df.dtype.kind + dtypes[0] = dtype + col_bounds[0] = (df.min(), df.max()) if dtype in "biufc" else (0, 0) + + df = df.iloc[roffset: roffset + rows, coffset: coffset + cols] if dim > 1 else df.iloc[roffset: roffset + rows] + rows = df.shape[0] + cols = df.shape[1] if dim > 1 else 1 + format = format.replace('%', '') + + def col_to_format(c): + return format if dtypes[c] == 'f' and format else array_default_format(dtypes[c]) + + xml += header_data_to_xml(rows, cols, dtypes, col_bounds, col_to_format, df, dim) + xml += array_data_to_xml(rows, cols, lambda r: (("%" + col_to_format(c)) % (df.iat[r, c] if dim > 1 else df.iat[r]) + for c in range(cols))) + return xml + + +def array_data_to_xml(rows, cols, get_row): + xml = "\n" % (rows, cols) + for row in range(rows): + xml += "\n" % to_string(row) + for value in get_row(row): xml += var_to_xml(value, '') return xml + + +def slice_to_xml(slice, rows, cols, format, type, bounds): + return '' % \ + (slice, rows, cols, format, type, bounds[1], bounds[0]) + + +def header_data_to_xml(rows, cols, dtypes, col_bounds, col_to_format, df, dim): + xml = "\n" % (rows, cols) + for col in range(cols): + col_label = get_label(df.axes[1].values[col]) if dim > 1 else str(col) + bounds = col_bounds[col] + xml += '\n' % \ + (str(col), col_label, dtypes[col], col_to_format(col), bounds[1], bounds[0]) + for row in range(rows): + xml += "\n" % (str(row), get_label(df.axes[0].values[row])) + xml += "\n" + return xml + +TYPE_TO_XML_CONVERTERS = {"ndarray": array_to_xml, "DataFrame": dataframe_to_xml, "Series": dataframe_to_xml} + + +def table_like_struct_to_xml(array, name, roffset, coffset, rows, cols, format): + _, type_name, _ = get_type(array) + if type_name in TYPE_TO_XML_CONVERTERS: + return "%s" % TYPE_TO_XML_CONVERTERS[type_name](array, name, roffset, coffset, rows, cols, format) + else: + raise VariableError("type %s not supported" % type_name) diff --git a/python/psi-api/src/com/jetbrains/python/PyTokenTypes.java b/python/psi-api/src/com/jetbrains/python/PyTokenTypes.java index 70f636351f3a..f35ce48bee2d 100644 --- a/python/psi-api/src/com/jetbrains/python/PyTokenTypes.java +++ b/python/psi-api/src/com/jetbrains/python/PyTokenTypes.java @@ -165,6 +165,10 @@ public class PyTokenTypes { public static final TokenSet WHITESPACE_OR_LINEBREAK = TokenSet.create(SPACE, TAB, FORMFEED, LINE_BREAK); public static final TokenSet OPEN_BRACES = TokenSet.create(LBRACKET, LBRACE, LPAR); public static final TokenSet CLOSE_BRACES = TokenSet.create(RBRACKET, RBRACE, RPAR); + + public static final TokenSet NUMERIC_LITERALS = TokenSet.create(FLOAT_LITERAL, INTEGER_LITERAL, IMAGINARY_LITERAL); + public static final TokenSet BOOL_LITERALS = TokenSet.create(TRUE_KEYWORD, FALSE_KEYWORD); + public static final TokenSet SCALAR_LITERALS = TokenSet.orSet(STRING_NODES, BOOL_LITERALS, NUMERIC_LITERALS, TokenSet.create(NONE_KEYWORD)); public static final TokenSet AUG_ASSIGN_OPERATIONS = TokenSet.create(PLUSEQ, MINUSEQ, MULTEQ, ATEQ, DIVEQ, PERCEQ, EXPEQ, GTGTEQ, LTLTEQ, ANDEQ, OREQ, XOREQ, FLOORDIVEQ); diff --git a/python/psi-api/src/com/jetbrains/python/psi/impl/PyOverridingTypeProvider.java b/python/psi-api/src/com/jetbrains/python/psi/impl/PyOverridingTypeProvider.java new file mode 100644 index 000000000000..ca59095b042c --- /dev/null +++ b/python/psi-api/src/com/jetbrains/python/psi/impl/PyOverridingTypeProvider.java @@ -0,0 +1,19 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.psi.impl; + +public interface PyOverridingTypeProvider extends PyTypeProvider { +} diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java b/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java index 09444ad6d618..546da2bd248d 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java @@ -1,6 +1,7 @@ package com.jetbrains.python.debugger; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; import com.intellij.icons.AllIcons; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; @@ -10,6 +11,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -17,6 +19,9 @@ import java.util.regex.Pattern; // todo: null modifier for modify modules, class objects etc. public class PyDebugValue extends XNamedValue { private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.pydev.PyDebugValue"); + private static final String DATA_FRAME = "DataFrame"; + private static final String SERIES = "Series"; + private static final Map EVALUATOR_POSTFIXES = ImmutableMap.of("ndarray", "Array", DATA_FRAME, DATA_FRAME, SERIES, SERIES); public static final int MAX_VALUE = 256; public static final String RETURN_VALUES_PREFIX = "__pydevd_ret_val_dict"; @@ -204,23 +209,16 @@ public class PyDebugValue extends XNamedValue { node.setPresentation(getValueIcon(), myType, value, myContainer); } - private boolean isDataFrame() { - return "DataFrame".equals(myType); - } - - private boolean isNdarray() { - return "ndarray".equals(myType); - } - private void setFullValueEvaluator(XValueNode node, String value) { String treeName = getFullTreeName(); - if (!isDataFrame() && !isNdarray()) { + String postfix = EVALUATOR_POSTFIXES.get(myType); + if (postfix == null) { if (value.length() >= MAX_VALUE) { node.setFullValueEvaluator(new PyFullValueEvaluator(myFrameAccessor, treeName)); } return; } - String linkText = "...View as " + (isDataFrame() ? "DataFrame" : "Array"); + String linkText = "...View as " + postfix; node.setFullValueEvaluator(new PyNumericContainerValueEvaluator(linkText, myFrameAccessor, treeName)); } diff --git a/python/src/META-INF/python-core-common.xml b/python/src/META-INF/python-core-common.xml index 1fcd94f60f08..eb4d7a1905f1 100644 --- a/python/src/META-INF/python-core-common.xml +++ b/python/src/META-INF/python-core-common.xml @@ -680,6 +680,7 @@ + diff --git a/python/src/com/jetbrains/numpy/codeInsight/NumpyResolveRater.java b/python/src/com/jetbrains/numpy/codeInsight/NumpyResolveRater.java index 1ab734ab0dde..6bf899f43c9c 100644 --- a/python/src/com/jetbrains/numpy/codeInsight/NumpyResolveRater.java +++ b/python/src/com/jetbrains/numpy/codeInsight/NumpyResolveRater.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ public class NumpyResolveRater extends PyResolveResultRaterBase { @Override public int getMemberRate(PsiElement member, PyType type, TypeEvalContext context) { if (member instanceof PsiNamedElement) { - final PyType ndArray = PyTypeParser.getTypeByName(member, NumpyDocStringTypeProvider.NDARRAY); + final PyType ndArray = PyTypeParser.getTypeByName(member, NumpyDocStringTypeProvider.NDARRAY, context); if (ndArray != null && PyTypeChecker.match(ndArray, type, context) && PyNames.isRightOperatorName(((PsiNamedElement)member).getName())) { return 100; diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index eea21e952154..f8f65fa4930b 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -150,6 +150,7 @@ QFIX.NAME.add.specifier=Add format specifier character QFIX.NAME.add.exception.base=Add Exception base class QFIX.NAME.change.signature=Change signature +QFIX.change.signature.of=Change signature of {0} QFIX.NAME.remove.argument=Remove argument diff --git a/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java b/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java index 2eea3ed647ff..44489ad8a501 100644 --- a/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java +++ b/python/src/com/jetbrains/python/codeInsight/controlflow/PyTypeAssertionEvaluator.java @@ -76,7 +76,7 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor { if (args.length == 1 && args[0] instanceof PyReferenceExpression) { final PyReferenceExpression target = (PyReferenceExpression)args[0]; - pushAssertion(target, myPositive, context -> PyTypeParser.getTypeByName(target, "collections." + PyNames.CALLABLE)); + pushAssertion(target, myPositive, context -> PyTypeParser.getTypeByName(target, "collections." + PyNames.CALLABLE, context)); } } } diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java index 2835e373a58c..818790416894 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyNamedTupleType.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,17 +39,25 @@ import java.util.Set; * @author yole */ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType { + + @NotNull + private final PsiElement myDeclaration; + + @NotNull private final String myName; - // 2 - namedtuple call itself - // 1 - return type of namedtuple call, aka namedtuple class - // 0 - namedtuple instance - private final int myDefinitionLevel; - private final PsiElement myDeclaration; + @NotNull private final List myFields; - public PyNamedTupleType(PyClass tupleClass, PsiElement declaration, String name, List fields, int definitionLevel) { - super(tupleClass, definitionLevel > 0); + @NotNull + private final DefinitionLevel myDefinitionLevel; + + public PyNamedTupleType(@NotNull PyClass tupleClass, + @NotNull PsiElement declaration, + @NotNull String name, + @NotNull List fields, + @NotNull DefinitionLevel definitionLevel) { + super(tupleClass, definitionLevel != DefinitionLevel.INSTANCE); myDeclaration = declaration; myFields = fields; myName = name; @@ -76,7 +84,7 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType @Override public Object[] getCompletionVariants(String completionPrefix, PsiElement location, ProcessingContext context) { - List result = new ArrayList<>(); + final List result = new ArrayList<>(); Collections.addAll(result, super.getCompletionVariants(completionPrefix, location, context)); for (String field : myFields) { result.add(LookupElementBuilder.create(field)); @@ -84,6 +92,7 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType return ArrayUtil.toObjectArray(result); } + @NotNull @Override public String getName() { return myName; @@ -97,22 +106,30 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType @Nullable @Override public PyType getCallType(@NotNull TypeEvalContext context, @NotNull PyCallSiteExpression callSite) { - if (myDefinitionLevel > 0) { - return new PyNamedTupleType(myClass, myDeclaration, myName, myFields, myDefinitionLevel - 1); + if (myDefinitionLevel == DefinitionLevel.AS_SUPERCLASS) { + return new PyNamedTupleType(myClass, myDeclaration, myName, myFields, DefinitionLevel.NEW_TYPE); } + else if (myDefinitionLevel == DefinitionLevel.NEW_TYPE) { + return new PyNamedTupleType(myClass, myDeclaration, myName, myFields, DefinitionLevel.INSTANCE); + } + return null; } @NotNull @Override public PyClassType toInstance() { - return myDefinitionLevel == 1 ? new PyNamedTupleType(myClass, myDeclaration, myName, myFields, 0) : this; + return myDefinitionLevel == DefinitionLevel.NEW_TYPE + ? new PyNamedTupleType(myClass, myDeclaration, myName, myFields, DefinitionLevel.INSTANCE) + : this; } @NotNull @Override public PyClassLikeType toClass() { - return myDefinitionLevel == 0 ? this : new PyNamedTupleType(myClass, myDeclaration, myName, myFields, 1); + return myDefinitionLevel == DefinitionLevel.INSTANCE + ? this + : new PyNamedTupleType(myClass, myDeclaration, myName, myFields, DefinitionLevel.NEW_TYPE); } @Override @@ -137,4 +154,11 @@ public class PyNamedTupleType extends PyClassTypeImpl implements PyCallableType public List getElementNames() { return Collections.unmodifiableList(myFields); } + + public enum DefinitionLevel { + + AS_SUPERCLASS, + NEW_TYPE, + INSTANCE + } } diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibOverridingTypeProvider.kt b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibOverridingTypeProvider.kt new file mode 100644 index 000000000000..90b7b71ad7b6 --- /dev/null +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibOverridingTypeProvider.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.codeInsight.stdlib + +import com.intellij.psi.PsiElement +import com.jetbrains.python.PyNames +import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider +import com.jetbrains.python.psi.PyFunction +import com.jetbrains.python.psi.impl.PyOverridingTypeProvider +import com.jetbrains.python.psi.types.PyType +import com.jetbrains.python.psi.types.PyTypeProviderBase +import com.jetbrains.python.psi.types.TypeEvalContext + +class PyStdlibOverridingTypeProvider : PyTypeProviderBase(), PyOverridingTypeProvider { + + override fun getReferenceType(referenceTarget: PsiElement, context: TypeEvalContext, anchor: PsiElement?): PyType? { + return if (isTypingNamedTupleInit(referenceTarget)) PyStdlibTypeProvider.getNamedTupleType(referenceTarget, context, anchor) else null + } + + private fun isTypingNamedTupleInit(referenceTarget: PsiElement): Boolean { + return referenceTarget is PyFunction && + PyNames.INIT == referenceTarget.name && + PyTypingTypeProvider.NAMEDTUPLE == referenceTarget.containingClass?.qualifiedName + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java index 14fb0f2f313d..a11685feaa9d 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java @@ -151,7 +151,7 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { } } else if ("enum.EnumMeta.__members__".equals(name)) { - return PyTypeParser.getTypeByName(referenceTarget, "dict[str, unknown]"); + return PyTypeParser.getTypeByName(referenceTarget, "dict[str, unknown]", context); } } return null; @@ -163,7 +163,8 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { final String qname = function.getQualifiedName(); if (qname != null) { if (OPEN_FUNCTIONS.contains(qname) && callSite instanceof PyCallExpression) { - return getOpenFunctionType(qname, PyCallExpressionHelper.mapArguments(callSite, function, context).getMappedParameters(), callSite); + final PyCallExpressionHelper.ArgumentMappingResults mapping = PyCallExpressionHelper.mapArguments(callSite, function, context); + return getOpenFunctionType(qname, mapping.getMappedParameters(), callSite, context); } else if ("tuple.__init__".equals(qname) && callSite instanceof PyCallExpression) { return getTupleInitializationType((PyCallExpression)callSite, context); @@ -279,21 +280,21 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { } @Nullable - private static PyType getNamedTupleType(@NotNull PsiElement referenceTarget, - @NotNull TypeEvalContext context, - @Nullable PsiElement anchor) { + static PyType getNamedTupleType(@NotNull PsiElement referenceTarget, + @NotNull TypeEvalContext context, + @Nullable PsiElement anchor) { if (referenceTarget instanceof PyTargetExpression) { final PyTargetExpression target = (PyTargetExpression)referenceTarget; final PyTargetExpressionStub stub = target.getStub(); if (stub != null) { - return getNamedTupleTypeFromStub(target, stub.getCustomStub(PyNamedTupleStub.class), 1); + return getNamedTupleTypeFromStub(target, stub.getCustomStub(PyNamedTupleStub.class), PyNamedTupleType.DefinitionLevel.NEW_TYPE); } else { - return getNamedTupleTypeFromAST(target, context, 1); + return getNamedTupleTypeFromAST(target, context, PyNamedTupleType.DefinitionLevel.NEW_TYPE); } } else if (referenceTarget instanceof PyFunction && anchor instanceof PyCallExpression) { - return getNamedTupleTypeFromAST((PyCallExpression)anchor, context, 2); + return getNamedTupleTypeFromAST((PyCallExpression)anchor, context, PyNamedTupleType.DefinitionLevel.AS_SUPERCLASS); } return null; } @@ -301,7 +302,8 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { @NotNull private static Ref getOpenFunctionType(@NotNull String callQName, @NotNull Map arguments, - @NotNull PsiElement anchor) { + @NotNull PsiElement anchor, + @NotNull TypeEvalContext context) { String mode = "r"; for (Map.Entry entry : arguments.entrySet()) { final PyNamedParameter parameter = entry.getValue(); @@ -319,20 +321,20 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { if (LanguageLevel.forElement(anchor).isAtLeast(LanguageLevel.PYTHON30) || "io.open".equals(callQName) || "_io.open".equals(callQName)) { if (mode.contains("b")) { - return Ref.create(PyTypeParser.getTypeByName(anchor, PY3K_BINARY_FILE_TYPE)); + return Ref.create(PyTypeParser.getTypeByName(anchor, PY3K_BINARY_FILE_TYPE, context)); } else { - return Ref.create(PyTypeParser.getTypeByName(anchor, PY3K_TEXT_FILE_TYPE)); + return Ref.create(PyTypeParser.getTypeByName(anchor, PY3K_TEXT_FILE_TYPE, context)); } } - return Ref.create(PyTypeParser.getTypeByName(anchor, PY2K_FILE_TYPE)); + return Ref.create(PyTypeParser.getTypeByName(anchor, PY2K_FILE_TYPE, context)); } @Nullable private static PyType getNamedTupleTypeFromStub(@NotNull PsiElement referenceTarget, @Nullable PyNamedTupleStub stub, - int definitionLevel) { + @NotNull PyNamedTupleType.DefinitionLevel definitionLevel) { if (stub == null) { return null; } @@ -349,7 +351,7 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { @Nullable private static PyType getNamedTupleTypeFromAST(@NotNull PyTargetExpression expression, @NotNull TypeEvalContext context, - int definitionLevel) { + @NotNull PyNamedTupleType.DefinitionLevel definitionLevel) { if (context.maySwitchToAST(expression)) { return getNamedTupleTypeFromStub(expression, PyNamedTupleStubImpl.create(expression), definitionLevel); } @@ -360,7 +362,7 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { @Nullable private static PyType getNamedTupleTypeFromAST(@NotNull PyCallExpression expression, @NotNull TypeEvalContext context, - int definitionLevel) { + @NotNull PyNamedTupleType.DefinitionLevel definitionLevel) { if (context.maySwitchToAST(expression)) { return getNamedTupleTypeFromStub(expression, PyNamedTupleStubImpl.create(expression), definitionLevel); } diff --git a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java index b27b06811b83..d08af5e4b638 100644 --- a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java @@ -23,7 +23,10 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiPolyVariantReference; -import com.intellij.psi.util.*; +import com.intellij.psi.util.CachedValueProvider; +import com.intellij.psi.util.CachedValuesManager; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.QualifiedName; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashMap; import com.intellij.util.containers.HashSet; @@ -55,6 +58,8 @@ import static com.jetbrains.python.psi.PyUtil.as; public class PyTypingTypeProvider extends PyTypeProviderBase { private static final Object RECURSION_KEY = new Object(); + public static final String TYPING = "typing"; + public static final String GENERATOR = "typing.Generator"; public static final String ASYNC_GENERATOR = "typing.AsyncGenerator"; public static final String COROUTINE = "typing.Coroutine"; @@ -63,6 +68,8 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { public static final String TYPE = "typing.Type"; public static final String ANY = "typing.Any"; + public static final String NAMEDTUPLE_SIMPLE = "NamedTuple"; + public static final Pattern TYPE_COMMENT_PATTERN = Pattern.compile("# *type: *(.*)"); private static final ImmutableMap COLLECTION_CLASSES = ImmutableMap.builder() @@ -479,7 +486,7 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { if (parameterizedType != null) { return Ref.create(parameterizedType); } - final PyType builtinCollection = getBuiltinCollection(resolved); + final PyType builtinCollection = getBuiltinCollection(resolved, context.getTypeContext()); if (builtinCollection != null) { return Ref.create(builtinCollection); } @@ -743,10 +750,10 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { } @Nullable - private static PyType getBuiltinCollection(@NotNull PsiElement element) { + private static PyType getBuiltinCollection(@NotNull PsiElement element, @NotNull TypeEvalContext context) { final String collectionName = getQualifiedName(element); final String builtinName = COLLECTION_CLASSES.get(collectionName); - return builtinName != null ? PyTypeParser.getTypeByName(element, builtinName) : null; + return builtinName != null ? PyTypeParser.getTypeByName(element, builtinName, context) : null; } @NotNull diff --git a/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java b/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java index 39c85123ceb5..353a5a72c265 100644 --- a/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java +++ b/python/src/com/jetbrains/python/console/PydevConsoleRunnerImpl.java @@ -48,7 +48,6 @@ import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Caret; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.EditorSettings; import com.intellij.openapi.editor.actionSystem.EditorAction; import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler; import com.intellij.openapi.editor.actions.SplitLineAction; @@ -784,7 +783,7 @@ public class PydevConsoleRunnerImpl implements PydevConsoleRunner { SoftWrapAction() { super(ActionsBundle.actionText("EditorToggleUseSoftWraps"), ActionsBundle.actionDescription("EditorToggleUseSoftWraps"), AllIcons.Actions.ToggleSoftWrap); - myConsoleView.getEditor().getSettings().setUseSoftWraps(isSelected); + updateEditors(); } @Override @@ -792,16 +791,15 @@ public class PydevConsoleRunnerImpl implements PydevConsoleRunner { return isSelected; } + private void updateEditors() { + myConsoleView.getEditor().getSettings().setUseSoftWraps(isSelected); + myConsoleView.getConsoleEditor().getSettings().setUseSoftWraps(isSelected); + } + @Override public void setSelected(AnActionEvent e, boolean state) { isSelected = state; - EditorSettings editorSettings = getConsoleView().getEditor().getSettings(); - if (isSelected) { - editorSettings.setUseSoftWraps(true); - } - else { - editorSettings.setUseSoftWraps(false); - } + updateEditors(); myConsoleSettings.setUseSoftWraps(isSelected); } } diff --git a/python/src/com/jetbrains/python/debugger/PyCallSignatureTypeProvider.java b/python/src/com/jetbrains/python/debugger/PyCallSignatureTypeProvider.java index 41734caedd8a..335c9dc4e503 100644 --- a/python/src/com/jetbrains/python/debugger/PyCallSignatureTypeProvider.java +++ b/python/src/com/jetbrains/python/debugger/PyCallSignatureTypeProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ public class PyCallSignatureTypeProvider extends PyTypeProviderBase { if (name != null) { final String typeName = PySignatureCacheManager.getInstance(param.getProject()).findParameterType(func, name); if (typeName != null) { - final PyType type = PyTypeParser.getTypeByName(param, typeName); + final PyType type = PyTypeParser.getTypeByName(param, typeName, context); if (type != null) { return Ref.create(PyDynamicallyEvaluatedType.create(type)); } @@ -51,7 +51,7 @@ public class PyCallSignatureTypeProvider extends PyTypeProviderBase { if (signature != null && signature.getReturnType() != null) { final String typeName = signature.getReturnType().getTypeQualifiedName(); if (typeName != null) { - final PyType type = PyTypeParser.getTypeByName(function, typeName); + final PyType type = PyTypeParser.getTypeByName(function, typeName, context); if (type != null) { return Ref.create(PyDynamicallyEvaluatedType.create(type)); } diff --git a/python/src/com/jetbrains/python/debugger/PySignatureUtil.java b/python/src/com/jetbrains/python/debugger/PySignatureUtil.java deleted file mode 100644 index 21094b579750..000000000000 --- a/python/src/com/jetbrains/python/debugger/PySignatureUtil.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2000-2014 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.jetbrains.python.debugger; - -import com.google.common.collect.Collections2; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.PsiElement; -import com.jetbrains.python.PyNames; -import com.jetbrains.python.psi.PyClass; -import com.jetbrains.python.psi.PyFunction; -import com.jetbrains.python.psi.types.PyClassType; -import com.jetbrains.python.psi.types.PyType; -import com.jetbrains.python.psi.types.PyTypeParser; -import com.jetbrains.python.psi.types.PyUnionType; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * @author traff - */ -public class PySignatureUtil { - private PySignatureUtil() { - } - - @Nullable - public static String getShortestImportableName(@Nullable PsiElement anchor, @NotNull String type) { - final PyType pyType = PyTypeParser.getTypeByName(anchor, type); - if (pyType instanceof PyClassType) { - PyClass c = ((PyClassType)pyType).getPyClass(); - return c.getQualifiedName(); - } - - if (pyType != null) { - return getPrintableName(pyType); - } - else { - return type; - } - } - - private static String getPrintableName(PyType type) { - if (type instanceof PyUnionType) { - return StringUtil.join(Collections2.transform(((PyUnionType)type).getMembers(), input -> getPrintableName(input)), " or "); - } - else if (type != null) { - return type.getName(); - } - else { - return PyNames.UNKNOWN_TYPE; - } - } - - @Nullable - public static String getArgumentType(@NotNull PyFunction function, @NotNull String name) { - PySignatureCacheManager cacheManager = PySignatureCacheManager.getInstance(function.getProject()); - PySignature signature = cacheManager.findSignature(function); - if (signature != null) { - return getShortestImportableName(function, signature.getArgTypeQualifiedName(name)); - } - return null; - } -} diff --git a/python/src/com/jetbrains/python/debugger/containerview/DataViewStrategy.java b/python/src/com/jetbrains/python/debugger/containerview/DataViewStrategy.java index 1eb2678b3715..7f40c9caca3c 100644 --- a/python/src/com/jetbrains/python/debugger/containerview/DataViewStrategy.java +++ b/python/src/com/jetbrains/python/debugger/containerview/DataViewStrategy.java @@ -15,12 +15,13 @@ */ package com.jetbrains.python.debugger.containerview; -import com.intellij.util.containers.ContainerUtil; +import com.google.common.collect.ImmutableSet; import com.jetbrains.python.debugger.ArrayChunk; import com.jetbrains.python.debugger.PyDebugValue; import com.jetbrains.python.debugger.array.ArrayViewStrategy; import com.jetbrains.python.debugger.array.AsyncArrayTableModel; import com.jetbrains.python.debugger.dataframe.DataFrameViewStrategy; +import com.jetbrains.python.debugger.dataframe.SeriesViewStrategy; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -29,7 +30,7 @@ import java.util.Set; public abstract class DataViewStrategy { private static class StrategyHolder { - private static final Set STRATEGIES = ContainerUtil.newHashSet(new ArrayViewStrategy(), new DataFrameViewStrategy()); + private static final Set STRATEGIES = ImmutableSet.of(new ArrayViewStrategy(), new DataFrameViewStrategy(), new SeriesViewStrategy()); } public abstract AsyncArrayTableModel createTableModel(int rowCount, int columnCount, @NotNull PyDataViewerPanel panel, @NotNull PyDebugValue debugValue); @@ -41,6 +42,10 @@ public abstract class DataViewStrategy { @NotNull public abstract String getTypeName(); + public boolean showColumnHeader() { + return true; + } + /** * @return null if no strategy for this type */ diff --git a/python/src/com/jetbrains/python/debugger/containerview/PyDataViewerPanel.java b/python/src/com/jetbrains/python/debugger/containerview/PyDataViewerPanel.java index a61f40f4141c..3d4c187e1961 100644 --- a/python/src/com/jetbrains/python/debugger/containerview/PyDataViewerPanel.java +++ b/python/src/com/jetbrains/python/debugger/containerview/PyDataViewerPanel.java @@ -205,6 +205,7 @@ public class PyDataViewerPanel extends JPanel { if (myTable.getColumnCount() > 0) { myTable.setDefaultRenderer(myTable.getColumnClass(0), cellRenderer); } + myTable.setShowColumns(strategy.showColumnHeader()); }); } diff --git a/python/src/com/jetbrains/python/debugger/dataframe/SeriesViewStrategy.java b/python/src/com/jetbrains/python/debugger/dataframe/SeriesViewStrategy.java new file mode 100644 index 000000000000..1775ef163883 --- /dev/null +++ b/python/src/com/jetbrains/python/debugger/dataframe/SeriesViewStrategy.java @@ -0,0 +1,31 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.debugger.dataframe; + +import org.jetbrains.annotations.NotNull; + +public class SeriesViewStrategy extends DataFrameViewStrategy { + @NotNull + @Override + public String getTypeName() { + return "Series"; + } + + @Override + public boolean showColumnHeader() { + return false; + } +} diff --git a/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java b/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java index e3c2fae4e871..952e80035be6 100644 --- a/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java @@ -163,7 +163,7 @@ public class PyDocumentationBuilder { private void buildFromParameter(@NotNull final TypeEvalContext context, @Nullable final PsiElement outerElement, @NotNull final PsiElement elementDefinition) { myBody.addItem(combUp("Parameter " + PyUtil.getReadableRepr(elementDefinition, false))); - final boolean typeFromDocstringAdded = addTypeAndDescriptionFromDocstring((PyNamedParameter)elementDefinition); + final boolean typeFromDocstringAdded = addTypeAndDescriptionFromDocstring((PyNamedParameter)elementDefinition, context); if (outerElement instanceof PyExpression) { final PyType type = context.getType((PyExpression)outerElement); if (type != null) { @@ -409,11 +409,11 @@ public class PyDocumentationBuilder { } } - private void addPredefinedMethodDoc(PyFunction fun, String mothodName) { + private void addPredefinedMethodDoc(PyFunction fun, String methodName) { final PyClassType objectType = PyBuiltinCache.getInstance(fun).getObjectType(); // old- and new-style classes share the __xxx__ stuff if (objectType != null) { final PyClass objectClass = objectType.getPyClass(); - final PyFunction predefinedMethod = objectClass.findMethodByName(mothodName, false, null); + final PyFunction predefinedMethod = objectClass.findMethodByName(methodName, false, null); if (predefinedMethod != null) { final PyStringLiteralExpression predefinedDocstring = getEffectiveDocStringExpression(predefinedMethod); final String predefinedDoc = predefinedDocstring != null ? predefinedDocstring.getStringValue() : null; @@ -468,9 +468,10 @@ public class PyDocumentationBuilder { * Adds type and description representation from function docstring * * @param parameter parameter of a function + * @param context type evaluation context * @return true if type from docstring was added */ - private boolean addTypeAndDescriptionFromDocstring(@NotNull final PyNamedParameter parameter) { + private boolean addTypeAndDescriptionFromDocstring(@NotNull PyNamedParameter parameter, @NotNull TypeEvalContext context) { final PyFunction function = PsiTreeUtil.getParentOfType(parameter, PyFunction.class); if (function != null) { final String docString = PyPsiUtils.strValue(getEffectiveDocStringExpression(function)); @@ -480,7 +481,7 @@ public class PyDocumentationBuilder { final String description = typeAndDescr.second; if (type != null) { - final PyType pyType = PyTypeParser.getTypeByName(parameter, type); + final PyType pyType = PyTypeParser.getTypeByName(parameter, type, context); if (pyType instanceof PyClassType) { myBody.addItem(": ").addWith(new LinkWrapper(PythonDocumentationProvider.LINK_TYPE_PARAM), $(pyType.getName())); } diff --git a/python/src/com/jetbrains/python/documentation/docstrings/PyDocStringTypeProvider.java b/python/src/com/jetbrains/python/documentation/docstrings/PyDocStringTypeProvider.java index 2162d63865cb..a43bfa5936ae 100644 --- a/python/src/com/jetbrains/python/documentation/docstrings/PyDocStringTypeProvider.java +++ b/python/src/com/jetbrains/python/documentation/docstrings/PyDocStringTypeProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,7 +39,7 @@ public class PyDocStringTypeProvider extends PyTypeProviderBase { if (docString != null) { final String typeText = docString.getParamType(param.getName()); if (StringUtil.isNotEmpty(typeText)) { - final Ref typeRef = parseType(func, typeText); + final Ref typeRef = parseType(func, typeText, context); if (param.isPositionalContainer()) { return Ref.create(PyTypeUtil.toPositionalContainerType(param, typeRef.get())); @@ -63,7 +63,7 @@ public class PyDocStringTypeProvider extends PyTypeProviderBase { if (docString != null) { final String typeText = docString.getReturnType(); if (StringUtil.isNotEmpty(typeText)) { - return parseType(callable, typeText); + return parseType(callable, typeText, context); } } } @@ -71,8 +71,8 @@ public class PyDocStringTypeProvider extends PyTypeProviderBase { } @NotNull - private static Ref parseType(@NotNull PyCallable callable, String typeText) { - final PyType type = PyTypeParser.getTypeByName(callable, typeText); + private static Ref parseType(@NotNull PyCallable callable, @NotNull String typeText, @NotNull TypeEvalContext context) { + final PyType type = PyTypeParser.getTypeByName(callable, typeText, context); if (type != null) { type.assertValid("from docstring"); } diff --git a/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java b/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java index d3f80dc20f00..1d5c432431a3 100644 --- a/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java @@ -20,6 +20,7 @@ import com.intellij.codeInspection.LocalInspectionToolSession; import com.intellij.codeInspection.LocalQuickFix; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.lang.ASTNode; +import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElementVisitor; import com.intellij.psi.PsiPolyVariantReference; import com.intellij.psi.ResolveResult; @@ -28,6 +29,7 @@ import com.intellij.xml.util.XmlStringUtil; import com.jetbrains.python.PyBundle; import com.jetbrains.python.PyNames; import com.jetbrains.python.PyTokenTypes; +import com.jetbrains.python.inspections.quickfix.PyChangeSignatureQuickFix; import com.jetbrains.python.inspections.quickfix.PyRemoveArgumentQuickFix; import com.jetbrains.python.inspections.quickfix.PyRenameArgumentQuickFix; import com.jetbrains.python.psi.*; @@ -36,6 +38,7 @@ import com.jetbrains.python.psi.types.PyABCUtil; import com.jetbrains.python.psi.types.PyType; import com.jetbrains.python.psi.types.PyTypeChecker; import com.jetbrains.python.psi.types.TypeEvalContext; +import com.jetbrains.python.refactoring.changeSignature.PyChangeSignatureHandler; import one.util.streamex.StreamEx; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; @@ -207,7 +210,20 @@ public class PyArgumentListInspection extends PyInspection { // if there is only one mapping, we could suggest quick fixes final Set duplicateKeywords = getDuplicateKeywordArguments(node); - for (PyExpression argument : mappings.get(0).getUnmappedArguments()) { + final PyCallExpression.PyArgumentsMapping mapping = mappings.get(0); + if (!mapping.getUnmappedArguments().isEmpty() && mapping.getUnmappedParameters().isEmpty()) { + final PyCallExpression.PyMarkedCallee markedCallee = mapping.getMarkedCallee(); + if (markedCallee != null) { + final PyCallable callable = markedCallee.getCallable(); + final Project project = node.getProject(); + if (callable instanceof PyFunction && !PyChangeSignatureHandler.isNotUnderSourceRoot(project, callable.getContainingFile())) { + holder.registerProblem(node, PyBundle.message("INSP.unexpected.arg(s)"), PyChangeSignatureQuickFix.forMismatchedCall(mapping)); + } + } + } + + + for (PyExpression argument : mapping.getUnmappedArguments()) { final List quickFixes = Lists.newArrayList(new PyRemoveArgumentQuickFix()); if (argument instanceof PyKeywordArgument) { if (duplicateKeywords.contains(((PyKeywordArgument)argument).getKeyword())) { @@ -225,7 +241,7 @@ public class PyArgumentListInspection extends PyInspection { holder.registerProblem(node, addPossibleCalleesRepresentationAndWrapInHtml(PyBundle.message("INSP.unexpected.arg(s)"), mappings, context)); } } - + private static void highlightUnfilledParameters(@NotNull PyArgumentList node, @NotNull ProblemsHolder holder, @NotNull List mappings, diff --git a/python/src/com/jetbrains/python/inspections/PyDocstringTypesInspection.java b/python/src/com/jetbrains/python/inspections/PyDocstringTypesInspection.java index 13fab23dce28..58b69113132f 100644 --- a/python/src/com/jetbrains/python/inspections/PyDocstringTypesInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyDocstringTypesInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,19 +20,18 @@ import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; import com.jetbrains.python.PyBundle; +import com.jetbrains.python.PyNames; import com.jetbrains.python.debugger.PySignature; import com.jetbrains.python.debugger.PySignatureCacheManager; -import com.jetbrains.python.debugger.PySignatureUtil; import com.jetbrains.python.documentation.docstrings.DocStringUtil; import com.jetbrains.python.documentation.docstrings.PlainDocString; import com.jetbrains.python.psi.PyElementGenerator; import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.psi.PyStringLiteralExpression; import com.jetbrains.python.psi.StructuredDocString; -import com.jetbrains.python.psi.types.PyType; -import com.jetbrains.python.psi.types.PyTypeChecker; -import com.jetbrains.python.psi.types.PyTypeParser; +import com.jetbrains.python.psi.types.*; import com.jetbrains.python.toolbox.Substring; +import one.util.streamex.StreamEx; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -94,7 +93,7 @@ public class PyDocstringTypesInspection extends PyInspection { if (type != null) { String dynamicType = signature.getArgTypeQualifiedName(param); if (dynamicType != null) { - String dynamicTypeShortName = PySignatureUtil.getShortestImportableName(function, dynamicType); + String dynamicTypeShortName = getShortestImportableName(function, dynamicType); if (!match(function, dynamicType, type.getValue())) { registerProblem(node, "Dynamically inferred type '" + dynamicTypeShortName + @@ -109,9 +108,40 @@ public class PyDocstringTypesInspection extends PyInspection { } } + @Nullable + private String getShortestImportableName(@Nullable PsiElement anchor, @NotNull String type) { + final PyType pyType = PyTypeParser.getTypeByName(anchor, type, myTypeEvalContext); + if (pyType instanceof PyClassType) { + return ((PyClassType)pyType).getPyClass().getQualifiedName(); + } + + if (pyType != null) { + return getPrintableName(pyType); + } + else { + return type; + } + } + + @Nullable + private static String getPrintableName(@Nullable PyType type) { + if (type instanceof PyUnionType) { + return StreamEx + .of(((PyUnionType)type).getMembers()) + .map(Visitor::getPrintableName) + .joining(" or "); + } + else if (type != null) { + return type.getName(); + } + else { + return PyNames.UNKNOWN_TYPE; + } + } + private boolean match(PsiElement anchor, String dynamicTypeName, String specifiedTypeName) { - final PyType dynamicType = PyTypeParser.getTypeByName(anchor, dynamicTypeName); - final PyType specifiedType = PyTypeParser.getTypeByName(anchor, specifiedTypeName); + final PyType dynamicType = PyTypeParser.getTypeByName(anchor, dynamicTypeName, myTypeEvalContext); + final PyType specifiedType = PyTypeParser.getTypeByName(anchor, specifiedTypeName, myTypeEvalContext); return PyTypeChecker.match(specifiedType, dynamicType, myTypeEvalContext); } } diff --git a/python/src/com/jetbrains/python/inspections/PyInitNewSignatureInspection.java b/python/src/com/jetbrains/python/inspections/PyInitNewSignatureInspection.java index ed5a883e9e7c..ace34764820b 100644 --- a/python/src/com/jetbrains/python/inspections/PyInitNewSignatureInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyInitNewSignatureInspection.java @@ -61,16 +61,16 @@ public class PyInitNewSignatureInspection extends PyInspection { if (!PyNames.NEW.equals(functionName) && !PyNames.INIT.equals(functionName)) return; final PyClass cls = node.getContainingClass(); if (cls == null) return; - if (!cls.isNewStyleClass(null)) return; + if (!cls.isNewStyleClass(myTypeEvalContext)) return; final String complementaryName = PyNames.NEW.equals(functionName) ? PyNames.INIT : PyNames.NEW; - final PyFunction complementaryMethod = cls.findMethodByName(complementaryName, true, null); + final PyFunction complementaryMethod = cls.findMethodByName(complementaryName, true, myTypeEvalContext); if (complementaryMethod == null || PyUtil.isObjectClass(assertNotNull(complementaryMethod.getContainingClass()))) return; if (!PyUtil.isSignatureCompatibleTo(complementaryMethod, node, myTypeEvalContext) && !PyUtil.isSignatureCompatibleTo(node, complementaryMethod, myTypeEvalContext) && node.getContainingFile() == cls.getContainingFile()) { - registerProblem(node.getParameterList(), PyNames.NEW.equals(node.getName()) ? PyBundle.message("INSP.new.incompatible.to.init") : - PyBundle.message("INSP.init.incompatible.to.new"), - new PyChangeSignatureQuickFix(false)); + registerProblem(node.getParameterList(), PyBundle.message(PyNames.NEW.equals(node.getName()) ? "INSP.new.incompatible.to.init" + : "INSP.init.incompatible.to.new"), + PyChangeSignatureQuickFix.forMismatchingMethods(node, complementaryMethod)); } } } diff --git a/python/src/com/jetbrains/python/inspections/PyMethodOverridingInspection.java b/python/src/com/jetbrains/python/inspections/PyMethodOverridingInspection.java index 9540013c42e0..cf335472c09f 100644 --- a/python/src/com/jetbrains/python/inspections/PyMethodOverridingInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyMethodOverridingInspection.java @@ -69,7 +69,7 @@ public class PyMethodOverridingInspection extends PyInspection { final String msg = PyBundle.message("INSP.signature.mismatch", cls.getName() + "." + name + "()", baseClass != null ? baseClass.getName() : ""); - registerProblem(function.getParameterList(), msg, new PyChangeSignatureQuickFix(true)); + registerProblem(function.getParameterList(), msg, PyChangeSignatureQuickFix.forMismatchingMethods(function, baseMethod)); } } } diff --git a/python/src/com/jetbrains/python/inspections/PyStringFormatInspection.java b/python/src/com/jetbrains/python/inspections/PyStringFormatInspection.java index e942a9755639..4977c1495d19 100644 --- a/python/src/com/jetbrains/python/inspections/PyStringFormatInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyStringFormatInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -118,7 +118,7 @@ public class PyStringFormatInspection extends PyInspection { final PyType elementType = tupleType.getElementType(i); if (elementType != null) { final String typeName = myFormatSpec.get(String.valueOf(i + 1)); - final PyType type = typeName != null ? PyTypeParser.getTypeByName(problemTarget, typeName) : null; + final PyType type = typeName != null ? PyTypeParser.getTypeByName(problemTarget, typeName, myTypeEvalContext) : null; checkTypeCompatible(problemTarget, elementType, type); } } @@ -171,7 +171,7 @@ public class PyStringFormatInspection extends PyInspection { else if (PyUtil.instanceOf(rightExpression, PySequenceExpression.class, PyComprehensionElement.class)) { if (s != null) { checkTypeCompatible(problemTarget, builtinCache.getStrType(), - PyTypeParser.getTypeByName(problemTarget, s)); + PyTypeParser.getTypeByName(problemTarget, s, myTypeEvalContext)); return 1; } } @@ -184,7 +184,7 @@ public class PyStringFormatInspection extends PyInspection { if (PyTypeChecker.match(listType, type, myTypeEvalContext) || PyTypeChecker.match(stringType, type, myTypeEvalContext)) { checkTypeCompatible(problemTarget, builtinCache.getStrType(), - PyTypeParser.getTypeByName(problemTarget, s)); + PyTypeParser.getTypeByName(problemTarget, s, myTypeEvalContext)); return 1; } PySliceItem sliceItem = ((PySliceExpression)rightExpression).getSliceItem(); @@ -328,7 +328,7 @@ public class PyStringFormatInspection extends PyInspection { @NotNull final String expectedTypeName, @NotNull PsiElement problemTarget) { final PyType actual = myTypeEvalContext.getType(expression); - final PyType expected = PyTypeParser.getTypeByName(problemTarget, expectedTypeName); + final PyType expected = PyTypeParser.getTypeByName(problemTarget, expectedTypeName, myTypeEvalContext); if (actual != null) { checkTypeCompatible(problemTarget, actual, expected); } diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java index 1acbd0d2bcd0..0f61583d7b46 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyChangeSignatureQuickFix.java @@ -15,30 +15,114 @@ */ package com.jetbrains.python.inspections.quickfix; -import com.intellij.codeInspection.LocalQuickFix; -import com.intellij.codeInspection.ProblemDescriptor; +import com.google.common.collect.Iterators; +import com.google.common.collect.PeekingIterator; +import com.intellij.codeInspection.LocalQuickFixOnPsiElement; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; -import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.openapi.util.Conditions; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.SmartPointerManager; +import com.intellij.psi.SmartPsiElementPointer; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.HashSet; +import com.intellij.xml.util.XmlStringUtil; import com.jetbrains.python.PyBundle; -import com.jetbrains.python.PyNames; -import com.jetbrains.python.psi.PyClass; -import com.jetbrains.python.psi.PyFunction; -import com.jetbrains.python.psi.search.PySuperMethodsSearch; +import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.PyCallExpression.PyArgumentsMapping; +import com.jetbrains.python.psi.types.PyClassType; +import com.jetbrains.python.psi.types.PyType; +import com.jetbrains.python.psi.types.PyUnionType; import com.jetbrains.python.psi.types.TypeEvalContext; +import com.jetbrains.python.refactoring.NameSuggesterUtil; +import com.jetbrains.python.refactoring.PyRefactoringUtil; import com.jetbrains.python.refactoring.changeSignature.PyChangeSignatureDialog; import com.jetbrains.python.refactoring.changeSignature.PyMethodDescriptor; import com.jetbrains.python.refactoring.changeSignature.PyParameterInfo; -import org.jetbrains.annotations.NonNls; +import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import java.util.List; +import java.util.*; -public class PyChangeSignatureQuickFix implements LocalQuickFix { +import static com.jetbrains.python.psi.PyUtil.as; - private final boolean myOverridenMethod; +public class PyChangeSignatureQuickFix extends LocalQuickFixOnPsiElement { - public PyChangeSignatureQuickFix(boolean overriddenMethod) { - myOverridenMethod = overriddenMethod; + public static final Key CHANGE_SIGNATURE_ORIGINAL_CALL = Key.create("CHANGE_SIGNATURE_ORIGINAL_CALL"); + + @NotNull + public static PyChangeSignatureQuickFix forMismatchedCall(@NotNull PyArgumentsMapping mapping) { + assert mapping.getMarkedCallee() != null; + final PyFunction function = as(mapping.getMarkedCallee().getCallable(), PyFunction.class); + assert function != null; + final PyCallExpression callExpression = mapping.getCallExpression(); + int positionalParamAnchor = -1; + final PyParameter[] parameters = function.getParameterList().getParameters(); + for (PyParameter parameter : parameters) { + final PyNamedParameter namedParam = parameter.getAsNamed(); + final boolean isVararg = namedParam != null && (namedParam.isPositionalContainer() || namedParam.isKeywordContainer()); + if (parameter instanceof PySingleStarParameter || parameter.hasDefaultValue() || isVararg) { + break; + } + positionalParamAnchor++; + } + final List> newParameters = new ArrayList<>(); + final TypeEvalContext context = TypeEvalContext.userInitiated(function.getProject(), callExpression.getContainingFile()); + final Set usedParamNames = new HashSet<>(); + for (PyExpression arg : mapping.getUnmappedArguments()) { + if (arg instanceof PyKeywordArgument) { + final PyExpression value = ((PyKeywordArgument)arg).getValueExpression(); + final String valueText = value != null ? value.getText() : ""; + newParameters.add(Pair.create(parameters.length - 1, + new PyParameterInfo(-1, ((PyKeywordArgument)arg).getKeyword(), valueText, true))); + } + else { + final String paramName = generateParameterName(arg, function, usedParamNames, context); + newParameters.add(Pair.create(positionalParamAnchor, new PyParameterInfo(-1, paramName, arg.getText(), false))); + usedParamNames.add(paramName); + } + } + return new PyChangeSignatureQuickFix(function, newParameters, mapping.getCallExpression()); + } + + @NotNull + public static PyChangeSignatureQuickFix forMismatchingMethods(@NotNull PyFunction function, @NotNull PyFunction complementary) { + final int paramLength = function.getParameterList().getParameters().length; + final int complementaryParamLength = complementary.getParameterList().getParameters().length; + final List> extraParams; + if (complementaryParamLength > paramLength) { + extraParams = Collections.singletonList(Pair.create(paramLength - 1, new PyParameterInfo(-1, "**kwargs", "", false))); + } + else { + extraParams = Collections.emptyList(); + } + return new PyChangeSignatureQuickFix(function, extraParams, null); + } + + private final List> myExtraParameters; + private final SmartPsiElementPointer myOriginalCallExpression; + + + /** + * @param extraParameters new parameters anchored by indexes of the existing parameters they should be inserted after + * (-1 in case they should precede the first parameter) + */ + private PyChangeSignatureQuickFix(@NotNull PyFunction function, + @NotNull List> extraParameters, + @Nullable PyCallExpression expression) { + super(function); + myExtraParameters = ContainerUtil.sorted(extraParameters, Comparator.comparingInt(p -> p.getFirst())); + if (expression != null) { + myOriginalCallExpression = SmartPointerManager.getInstance(function.getProject()).createSmartPsiElementPointer(expression); + } + else { + myOriginalCallExpression = null; + } } @NotNull @@ -46,31 +130,114 @@ public class PyChangeSignatureQuickFix implements LocalQuickFix { return PyBundle.message("QFIX.NAME.change.signature"); } - public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) { - final PyFunction function = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PyFunction.class); - if (function == null) return; - final PyClass cls = function.getContainingClass(); - assert cls != null; - final String functionName = function.getName(); - final String complementaryName = PyNames.NEW.equals(functionName) ? PyNames.INIT : PyNames.NEW; - final TypeEvalContext context = TypeEvalContext.userInitiated(project, descriptor.getEndElement().getContainingFile()); - final PyFunction complementaryMethod = myOverridenMethod ? (PyFunction)PySuperMethodsSearch.search(function, context).findFirst() - : cls.findMethodByName(complementaryName, true, null); + @NotNull + @Override + public String getText() { + final PyFunction function = getFunction(); + if (function == null) { + return getFamilyName(); + } + final String params = StringUtil.join(createMethodDescriptor(function).getParameters(), info -> { + return info.getOldIndex() == -1 ? "" + info.getName() + "" : info.getName(); + }, ", "); - assert complementaryMethod != null; - final PyMethodDescriptor methodDescriptor = new PyMethodDescriptor(function) { + final String message = PyBundle.message("QFIX.change.signature.of", StringUtil.notNullize(function.getName()) + "(" + params + ")"); + return XmlStringUtil.wrapInHtml(message); + } + + @Nullable + private PyFunction getFunction() { + return (PyFunction)getStartElement(); + } + + @Override + public void invoke(@NotNull Project project, @NotNull PsiFile file, @NotNull PsiElement startElement, @NotNull PsiElement endElement) { + final PyFunction function = getFunction(); + final PyMethodDescriptor descriptor = createMethodDescriptor(function); + + final PyChangeSignatureDialog dialog = new PyChangeSignatureDialog(project, descriptor) { + // Similar to JavaChangeSignatureDialog.createAndPreselectNew() @Override - public List getParameters() { - final List parameterInfos = super.getParameters(); - final int paramLength = function.getParameterList().getParameters().length; - final int complementaryParamLength = complementaryMethod.getParameterList().getParameters().length; - if (complementaryParamLength > paramLength) - parameterInfos.add(new PyParameterInfo(-1, "**kwargs", "", false)); - return parameterInfos; + protected int getSelectedIdx() { + return (int)StreamEx.of(getParameters()).indexOf(info -> info.getOldIndex() < 0).orElse(super.getSelectedIdx()); } }; - final PyChangeSignatureDialog dialog = new PyChangeSignatureDialog(project, methodDescriptor); - dialog.show(); + + final PyCallExpression originalCall = myOriginalCallExpression.getElement(); + try { + if (originalCall != null) { + originalCall.putUserData(CHANGE_SIGNATURE_ORIGINAL_CALL, true); + } + if (ApplicationManager.getApplication().isUnitTestMode()) { + dialog.createRefactoringProcessor().run(); + } + else { + dialog.show(); + } + } + finally { + if (originalCall != null) { + originalCall.putUserData(CHANGE_SIGNATURE_ORIGINAL_CALL, null); + } + } + } + + @NotNull + private static String generateParameterName(@NotNull PyExpression argumentValue, + @NotNull PyFunction function, + @NotNull Set usedParameterNames, + @NotNull TypeEvalContext context) { + final Collection suggestions = new LinkedHashSet<>(); + final PyCallExpression callExpr = as(argumentValue, PyCallExpression.class); + final PyElement referenceElem = as(callExpr != null ? callExpr.getCallee() : argumentValue, PyReferenceExpression.class); + if (referenceElem != null) { + suggestions.addAll(NameSuggesterUtil.generateNames(referenceElem.getText())); + } + if (suggestions.isEmpty()) { + PyType type = context.getType(argumentValue); + if (type instanceof PyUnionType) { + type = ContainerUtil.find(((PyUnionType)type).getMembers(), Conditions.instanceOf(PyClassType.class)); + } + final String typeName = type != null && type.getName() != null ? type.getName() : "object"; + suggestions.addAll(NameSuggesterUtil.generateNamesByType(typeName)); + } + final String shortestName = Collections.min(suggestions, Comparator.comparingInt(String::length)); + + String result = shortestName; + int counter = 1; + while (!PyRefactoringUtil.isValidNewName(result, function.getStatementList()) || usedParameterNames.contains(result)) { + result = shortestName + counter; + counter++; + } + return result; + } + + @NotNull + private PyMethodDescriptor createMethodDescriptor(final PyFunction function) { + return new PyMethodDescriptor(function) { + @Override + public List getParameters() { + final List result = new ArrayList<>(); + final List originalParams = super.getParameters(); + final PeekingIterator> extra = Iterators.peekingIterator(myExtraParameters.iterator()); + while (extra.hasNext() && extra.peek().getFirst() < 0) { + result.add(extra.next().getSecond()); + } + for (int i = 0; i < originalParams.size(); i++) { + result.add(originalParams.get(i)); + while (extra.hasNext() && extra.peek().getFirst() == i) { + result.add(extra.next().getSecond()); + } + } + return result; + } + }; + } + + @Nullable + @Override + public PsiElement getElementToMakeWritable(@NotNull PsiFile currentFile) { + return getFunction(); } @Override diff --git a/python/src/com/jetbrains/python/psi/PyFileElementType.java b/python/src/com/jetbrains/python/psi/PyFileElementType.java index 11cd013273ee..e95590bbb9f3 100644 --- a/python/src/com/jetbrains/python/psi/PyFileElementType.java +++ b/python/src/com/jetbrains/python/psi/PyFileElementType.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,7 +62,7 @@ public class PyFileElementType extends IStubFileElementType { @Override public int getStubVersion() { // Don't forget to update versions of indexes that use the updated stub-based elements - return 58; + return 59; } @Nullable diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java index 7e734531e9af..43ec7333f878 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java +++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java @@ -473,6 +473,11 @@ public class PyCallExpressionHelper { @Nullable private static Ref getCallTargetReturnType(@NotNull PyCallExpression call, @NotNull PsiElement target, @NotNull TypeEvalContext context) { + final PyType providedOverridingType = PyReferenceExpressionImpl.getReferenceTypeFromOverridingProviders(target, context, call); + if (providedOverridingType instanceof PyCallableType) { + return Ref.create(((PyCallableType)providedOverridingType).getCallType(context, call)); + } + PyClass cls = null; PyFunction init = null; if (target instanceof PyClass) { diff --git a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java index 6b363f4e636b..50964690da49 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java @@ -41,6 +41,7 @@ import com.jetbrains.python.psi.impl.references.PyReferenceImpl; import com.jetbrains.python.psi.resolve.*; import com.jetbrains.python.psi.types.*; import com.jetbrains.python.refactoring.PyDefUseUtil; +import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -488,6 +489,18 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere return null; } + @Nullable + public static PyType getReferenceTypeFromOverridingProviders(@NotNull PsiElement target, + @NotNull TypeEvalContext context, + @Nullable PsiElement anchor) { + return StreamEx + .of(Extensions.getExtensions(PyTypeProvider.EP_NAME)) + .select(PyOverridingTypeProvider.class) + .map(provider -> provider.getReferenceType(target, context, anchor)) + .findFirst(Objects::nonNull) + .orElse(null); + } + @Nullable public static PyType getReferenceTypeFromProviders(@NotNull PsiElement target, @NotNull TypeEvalContext context, diff --git a/python/src/com/jetbrains/python/psi/impl/stubs/PyNamedTupleStubImpl.java b/python/src/com/jetbrains/python/psi/impl/stubs/PyNamedTupleStubImpl.java index c4a3ec6b6e37..e68dd73e5da8 100644 --- a/python/src/com/jetbrains/python/psi/impl/stubs/PyNamedTupleStubImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/stubs/PyNamedTupleStubImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,17 +15,21 @@ */ package com.jetbrains.python.psi.impl.stubs; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.stubs.StubInputStream; import com.intellij.psi.stubs.StubOutputStream; import com.intellij.psi.util.QualifiedName; +import com.intellij.util.ArrayUtil; import com.intellij.util.io.StringRef; import com.jetbrains.python.PyNames; +import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyPsiUtils; import com.jetbrains.python.psi.resolve.PyResolveUtil; import com.jetbrains.python.psi.stubs.PyNamedTupleStub; +import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -70,22 +74,22 @@ public class PyNamedTupleStubImpl implements PyNamedTupleStub { return null; } - final QualifiedName namedTupleQName = getNamedTupleQName(calleeReference); + final Pair calleeNameAndModule = getCalleeNameAndNTModule(calleeReference); - if (namedTupleQName != null) { + if (calleeNameAndModule != null) { final String name = resolveTupleName(expression); if (name == null) { return null; } - final List fields = resolveTupleFields(expression); + final List fields = resolveTupleFields(expression, calleeNameAndModule.getSecond()); if (fields == null) { return null; } - return new PyNamedTupleStubImpl(namedTupleQName, name, fields); + return new PyNamedTupleStubImpl(calleeNameAndModule.getFirst(), name, fields); } return null; @@ -144,14 +148,14 @@ public class PyNamedTupleStubImpl implements PyNamedTupleStub { } @Nullable - private static QualifiedName getNamedTupleQName(@NotNull PyReferenceExpression referenceExpression) { - final QualifiedName name = getFullyQualifiedNamedTupleQName(referenceExpression); + private static Pair getCalleeNameAndNTModule(@NotNull PyReferenceExpression referenceExpression) { + final Pair name = getFullyQCalleeNameAndNTModule(referenceExpression); if (name != null) { return name; } - return getImportedNamedTupleQName(referenceExpression); + return getImportedCalleeNameAndNTModule(referenceExpression); } @Nullable @@ -165,6 +169,13 @@ public class PyNamedTupleStubImpl implements PyNamedTupleStub { // Point = namedtuple(("Point"), ...) + // name = "Point" + // Point = NamedTuple(name, ...) + + // Point = NamedTuple("Point", ...) + + // Point = NamedTuple(("Point"), ...) + final PyExpression nameExpression = PyPsiUtils.flattenParens(callExpression.getArgument(0, PyExpression.class)); if (nameExpression instanceof PyReferenceExpression) { @@ -175,27 +186,15 @@ public class PyNamedTupleStubImpl implements PyNamedTupleStub { } @Nullable - private static List resolveTupleFields(@NotNull PyCallExpression callExpression) { - // SUPPORTED CASES: - - // fields = ["x", "y"] - // Point = namedtuple(..., fields) - - // Point = namedtuple(..., "x y") - - // Point = namedtuple(..., ("x y")) - - // Point = namedtuple(..., "x, y") - - // Point = namedtuple(..., ["x", "y"]) - - final PyExpression fieldsExpression = PyPsiUtils.flattenParens(callExpression.getArgument(1, PyExpression.class)); - - if (fieldsExpression instanceof PyReferenceExpression) { - return extractFields(fullResolveLocally((PyReferenceExpression)fieldsExpression)); + private static List resolveTupleFields(@NotNull PyCallExpression callExpression, @NotNull NamedTupleModule module) { + switch (module) { + case TYPING: + return resolveTypingNTFields(callExpression); + case COLLECTIONS: + return resolveCollectionsNTFields(callExpression); + default: + return null; } - - return extractFields(fieldsExpression); } @NotNull @@ -214,7 +213,7 @@ public class PyNamedTupleStubImpl implements PyNamedTupleStub { } @Nullable - private static QualifiedName getFullyQualifiedNamedTupleQName(@NotNull PyReferenceExpression referenceExpression) { + private static Pair getFullyQCalleeNameAndNTModule(@NotNull PyReferenceExpression referenceExpression) { // SUPPORTED CASES: // import collections @@ -223,14 +222,27 @@ public class PyNamedTupleStubImpl implements PyNamedTupleStub { // import collections as c // Point = c.namedtuple(...) - if (PyNames.NAMEDTUPLE.equals(referenceExpression.getName())) { + // import typing + // ... = typing.NamedTuple(...) + + // import typing as t + // ... = t.NamedTuple(...) + + final String referenceName = referenceExpression.getName(); + final NamedTupleModule module = PyNames.NAMEDTUPLE.equals(referenceName) + ? NamedTupleModule.COLLECTIONS + : PyTypingTypeProvider.NAMEDTUPLE_SIMPLE.equals(referenceName) + ? NamedTupleModule.TYPING + : null; + + if (module != null) { final PyExpression qualifier = referenceExpression.getQualifier(); if (qualifier instanceof PyReferenceExpression) { final PyReferenceExpression qualifierReference = (PyReferenceExpression)qualifier; - if (!qualifierReference.isQualified() && resolvesToCollections(qualifierReference)) { - return QualifiedName.fromComponents(qualifierReference.getName(), referenceExpression.getName()); + if (!qualifierReference.isQualified() && resolvesToModule(qualifierReference, module)) { + return Pair.createNonNull(QualifiedName.fromComponents(qualifierReference.getName(), referenceName), module); } } } @@ -239,7 +251,7 @@ public class PyNamedTupleStubImpl implements PyNamedTupleStub { } @Nullable - private static QualifiedName getImportedNamedTupleQName(@NotNull PyReferenceExpression referenceExpression) { + private static Pair getImportedCalleeNameAndNTModule(@NotNull PyReferenceExpression referenceExpression) { // SUPPORTED CASES: // from collections import namedtuple @@ -248,18 +260,31 @@ public class PyNamedTupleStubImpl implements PyNamedTupleStub { // from collections import namedtuple as NT // Point = NT(...) + // from typing import NamedTuple + // Point = NamedTuple(...) + + // from typing import NamedTuple as NT + // Point = NT(...) + for (PsiElement element : PyResolveUtil.resolveLocally(referenceExpression)) { if (element instanceof PyImportElement) { final PyImportElement importElement = (PyImportElement)element; + final QualifiedName importedQName = importElement.getImportedQName(); - if (equals(importElement.getImportedQName(), PyNames.NAMEDTUPLE)) { + final NamedTupleModule module = equals(importedQName, PyNames.NAMEDTUPLE) + ? NamedTupleModule.COLLECTIONS + : equals(importedQName, PyTypingTypeProvider.NAMEDTUPLE_SIMPLE) + ? NamedTupleModule.TYPING + : null; + + if (module != null) { final PyStatement importStatement = importElement.getContainingImportStatement(); if (importStatement instanceof PyFromImportStatement) { final PyFromImportStatement fromImportStatement = (PyFromImportStatement)importStatement; - if (equals(fromImportStatement.getImportSourceQName(), PyNames.COLLECTIONS)) { - return QualifiedName.fromComponents(referenceExpression.getName()); + if (equals(fromImportStatement.getImportSourceQName(), module.getModuleName())) { + return Pair.createNonNull(QualifiedName.fromComponents(referenceExpression.getName()), module); } } } @@ -269,12 +294,12 @@ public class PyNamedTupleStubImpl implements PyNamedTupleStub { return null; } - private static boolean resolvesToCollections(@NotNull PyReferenceExpression referenceExpression) { + private static boolean resolvesToModule(@NotNull PyReferenceExpression referenceExpression, @NotNull NamedTupleModule module) { for (PsiElement element : PyResolveUtil.resolveLocally(referenceExpression)) { if (element instanceof PyImportElement) { final PyImportElement importElement = (PyImportElement)element; - if (equals(importElement.getImportedQName(), PyNames.COLLECTIONS)) { + if (equals(importElement.getImportedQName(), module.getModuleName())) { return true; } } @@ -305,32 +330,98 @@ public class PyNamedTupleStubImpl implements PyNamedTupleStub { } @Nullable - private static List extractFields(@Nullable PyExpression expression) { - if (expression == null) { - return null; - } + private static List resolveCollectionsNTFields(@NotNull PyCallExpression callExpression) { + // SUPPORTED CASES: - final List listValue = PyUtil.strListValue(expression); + // fields = ["x", "y"] + // Point = namedtuple(..., fields) - if (listValue != null) { - return listValue; - } + // Point = namedtuple(..., "x y") - return extractFields(PyPsiUtils.strValue(expression)); + // Point = namedtuple(..., ("x y")) + + // Point = namedtuple(..., "x, y") + + // Point = namedtuple(..., ["x", "y"]) + + final PyExpression fields = PyPsiUtils.flattenParens(callExpression.getArgument(1, PyExpression.class)); + + final PyExpression resolvedFields = fields instanceof PyReferenceExpression + ? fullResolveLocally((PyReferenceExpression)fields) + : fields; + + final List listValue = PyUtil.strListValue(resolvedFields); + if (listValue != null) return listValue; + + final String resolvedFieldsValue = PyPsiUtils.strValue(resolvedFields); + if (resolvedFieldsValue == null) return null; + + return StreamEx + .of(StringUtil.tokenize(resolvedFieldsValue, ", ").iterator()) + .toList(); } @Nullable - private static List extractFields(@Nullable String fieldsString) { - if (fieldsString == null) { - return null; + private static List resolveTypingNTFields(@NotNull PyCallExpression callExpression) { + // SUPPORTED CASES: + + // fields = [("x", str), ("y", int)] + // Point = NamedTuple(..., fields) + + // Point = NamedTuple(..., [("x", str), ("y", int)]) + + // Point = NamedTuple(..., x=str, y=int) + + final PyExpression secondArgument = PyPsiUtils.flattenParens(callExpression.getArgument(1, PyExpression.class)); + + if (secondArgument instanceof PyKeywordArgument) { + final PyExpression[] arguments = callExpression.getArguments(); + return StreamEx + .of(arguments, 1, arguments.length) + .select(PyKeywordArgument.class) + .map(PyKeywordArgument::getKeyword) + .toList(); + } else { + final PyExpression resolvedFields = secondArgument instanceof PyReferenceExpression + ? fullResolveLocally((PyReferenceExpression)secondArgument) + : secondArgument; + if (!(resolvedFields instanceof PySequenceExpression)) return null; + + final List result = new ArrayList<>(); + + for (PyExpression element : ((PySequenceExpression)resolvedFields).getElements()) { + if (!(element instanceof PyParenthesizedExpression)) return null; + + final PyExpression contained = ((PyParenthesizedExpression)element).getContainedExpression(); + if (!(contained instanceof PyTupleExpression)) return null; + + final PyExpression[] nameAndType = ((PyTupleExpression)contained).getElements(); + final PyExpression name = ArrayUtil.getFirstElement(nameAndType); + if (nameAndType.length != 2 || !(name instanceof PyStringLiteralExpression)) return null; + + result.add(((PyStringLiteralExpression)name).getStringValue()); + } + + return result; } + } - final List result = new ArrayList<>(); + private enum NamedTupleModule { - for (String name : StringUtil.tokenize(fieldsString, ", ")) { - result.add(name); - } + COLLECTIONS { + @Override + public String getModuleName() { + return PyNames.COLLECTIONS; + } + }, - return result; + TYPING { + @Override + public String getModuleName() { + return PyTypingTypeProvider.TYPING; + } + }; + + public abstract String getModuleName(); } } diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeParser.java b/python/src/com/jetbrains/python/psi/types/PyTypeParser.java index f82c0310924b..1499e771c542 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeParser.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -116,25 +116,45 @@ public class PyTypeParser { * @return null either if there was an error during parsing or if extracted type is equivalent to Any or undefined */ @Nullable - public static PyType getTypeByName(@Nullable final PsiElement anchor, @NotNull String type) { + public static PyType getTypeByName(@Nullable PsiElement anchor, @NotNull String type) { + if (anchor == null) return EMPTY_RESULT.getType(); return parse(anchor, type).getType(); } /** - * @param anchor should never be null or null will be returned + * @param anchor should never be null or null will be returned + * @param context type evaluation context + * @return null either if there was an error during parsing or if extracted type is equivalent to Any or undefined + */ + @Nullable + public static PyType getTypeByName(@Nullable PsiElement anchor, @NotNull String type, @NotNull TypeEvalContext context) { + if (anchor == null) return EMPTY_RESULT.getType(); + return parse(anchor, type, context).getType(); + } + + /** + * @param anchor should never be null or {@link PyTypeParser#EMPTY_RESULT} will be returned + * @param type representation of the type to parse */ @NotNull - public static ParseResult parse(@Nullable final PsiElement anchor, @NotNull String type) { + public static ParseResult parse(@NotNull PsiElement anchor, @NotNull String type) { + return parse(anchor, type, TypeEvalContext.codeInsightFallback(anchor.getProject())); + } + + /** + * @param anchor should never be null or {@link PyTypeParser#EMPTY_RESULT} will be returned + * @param type representation of the type to parse + * @param context type evaluation context + */ + @NotNull + public static ParseResult parse(@NotNull PsiElement anchor, @NotNull String type, @NotNull TypeEvalContext context) { PyPsiUtils.assertValid(anchor); - if (anchor == null) { - return EMPTY_RESULT; - } final ForwardDeclaration typeExpr = ForwardDeclaration.create(); final FunctionalParser classType = token(IDENTIFIER).then(many(op(".").skipThen(token(IDENTIFIER)))) - .map(new MakeSimpleType(anchor)) + .map(new MakeSimpleType(anchor, context)) .cached() .named("class-type"); @@ -304,9 +324,11 @@ public class PyTypeParser { private static class MakeSimpleType implements Function, List>>, ParseResult> { @NotNull private final PsiElement myAnchor; + @NotNull private final TypeEvalContext myContext; - public MakeSimpleType(@NotNull PsiElement anchor) { + public MakeSimpleType(@NotNull PsiElement anchor, @NotNull TypeEvalContext context) { myAnchor = anchor; + myContext = context; } @Nullable @@ -331,15 +353,14 @@ public class PyTypeParser { if (file instanceof PyFile) { final PyFile pyFile = (PyFile)file; - final TypeEvalContext context = TypeEvalContext.codeInsightFallback(file.getProject()); final Map types = new HashMap<>(); final Map fullRanges = new HashMap<>(); final Map imports = new HashMap<>(); - PyType type = resolveQualifierType(tokens, pyFile, context, types, fullRanges, imports); + PyType type = resolveQualifierType(tokens, pyFile, myContext, types, fullRanges, imports); if (type != null) { - final PyResolveContext resolveContext = PyResolveContext.defaultContext().withTypeEvalContext(context); + final PyResolveContext resolveContext = PyResolveContext.defaultContext().withTypeEvalContext(myContext); final PyExpression expression = myAnchor instanceof PyExpression ? (PyExpression)myAnchor : null; for (Token token : tokens) { @@ -350,7 +371,7 @@ public class PyTypeParser { if (results != null && !results.isEmpty()) { final PsiElement resolved = results.get(0).getElement(); if (resolved instanceof PyTypedElement) { - type = context.getType((PyTypedElement)resolved); + type = myContext.getType((PyTypedElement)resolved); if (type != null && !allowResolveToType(type)) { type = null; break; diff --git a/python/src/com/jetbrains/python/refactoring/PyReplaceExpressionUtil.java b/python/src/com/jetbrains/python/refactoring/PyReplaceExpressionUtil.java index 31017cda1cc6..13365921cf80 100644 --- a/python/src/com/jetbrains/python/refactoring/PyReplaceExpressionUtil.java +++ b/python/src/com/jetbrains/python/refactoring/PyReplaceExpressionUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,8 +22,6 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.tree.IElementType; import com.jetbrains.python.PyElementTypes; -import com.jetbrains.python.PythonStringUtil; -import com.jetbrains.python.inspections.PyStringFormatParser; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.impl.PyPsiUtils; @@ -154,7 +152,7 @@ public class PyReplaceExpressionUtil implements PyElementTypes { final PyType valueType = context.getType(formatValue); final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(oldExpression); final PyType tupleType = builtinCache.getTupleType(); - final PyType mappingType = PyTypeParser.getTypeByName(null, "collections.Mapping"); + final PyType mappingType = PyTypeParser.getTypeByName(null, "collections.Mapping", context); if (!PyTypeChecker.match(tupleType, valueType, context) || (mappingType != null && !PyTypeChecker.match(mappingType, valueType, context))) { return replaceSubstringWithSingleValueFormatting(oldExpression, textRange, prefix, suffix, formatValue, newText, substitutions); @@ -322,12 +320,12 @@ public class PyReplaceExpressionUtil implements PyElementTypes { builder.append("("); } if (!leftQuote.endsWith(prefix)) { - builder.append(prefix + rightQuote + " + "); + builder.append(prefix).append(rightQuote).append(" + "); } final int pos = builder.toString().length(); builder.append(newText); if (!rightQuote.startsWith(suffix)) { - builder.append(" + " + leftQuote + suffix); + builder.append(" + ").append(leftQuote).append(suffix); } if (hasSubstitutions) { builder.append(")"); diff --git a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java index 0d3857a0f85d..09ba82f0c219 100644 --- a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java +++ b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureDialog.java @@ -38,13 +38,15 @@ import com.intellij.refactoring.ui.VisibilityPanelBase; import com.intellij.ui.EditorTextField; import com.intellij.ui.components.JBLabel; import com.intellij.ui.treeStructure.Tree; +import com.intellij.util.ArrayUtil; import com.intellij.util.Consumer; import com.intellij.util.IJSwingUtilities; import com.intellij.util.containers.HashSet; import com.intellij.util.ui.UIUtil; -import com.intellij.util.ui.table.JBListTable; +import com.intellij.util.ui.table.EditorTextFieldJBTableRowRenderer; import com.intellij.util.ui.table.JBTableRow; import com.intellij.util.ui.table.JBTableRowEditor; +import com.intellij.util.ui.table.JBTableRowRenderer; import com.jetbrains.python.PyBundle; import com.jetbrains.python.PyNames; import com.jetbrains.python.PythonFileType; @@ -53,7 +55,7 @@ import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.psi.PyParameterList; import com.jetbrains.python.refactoring.introduce.IntroduceValidator; -import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; @@ -68,10 +70,10 @@ import java.util.Set; * User : ktisha */ -public class PyChangeSignatureDialog extends ChangeSignatureDialogBase { +public class PyChangeSignatureDialog extends + ChangeSignatureDialogBase { - public PyChangeSignatureDialog(Project project, - PyMethodDescriptor method) { + public PyChangeSignatureDialog(Project project, PyMethodDescriptor method) { super(project, method, false, method.getMethod().getContext()); } @@ -87,7 +89,7 @@ public class PyChangeSignatureDialog extends ChangeSignatureDialogBase parameters = getParameters(); return new PyChangeSignatureProcessor(myProject, myMethod.getMethod(), getMethodName(), parameters.toArray(new PyParameterInfo[parameters.size()])); @@ -107,9 +109,7 @@ public class PyChangeSignatureDialog extends ChangeSignatureDialogBase parameters = myParametersTableModel.getItems(); - Set parameterNames = new HashSet<>(); + final Set parameterNames = new HashSet<>(); boolean hadPositionalContainer = false; boolean hadKeywordContainer = false; boolean hadDefaultValue = false; boolean hadSingleStar = false; boolean hadParamsAfterSingleStar = false; - LanguageLevel languageLevel = LanguageLevel.forElement(myMethod.getMethod()); + final LanguageLevel languageLevel = LanguageLevel.forElement(myMethod.getMethod()); - int parametersLength = parameters.size(); + final int parametersLength = parameters.size(); - for (int index = 0; index != parametersLength; ++index) { - PyParameterTableModelItem info = parameters.get(index); + for (int index = 0; index < parametersLength; index++) { + final PyParameterTableModelItem info = parameters.get(index); final PyParameterInfo parameter = info.parameter; final String name = parameter.getName(); final String nameWithoutStars = StringUtil.trimLeading(name, '*').trim(); @@ -148,7 +148,7 @@ public class PyChangeSignatureDialog extends ChangeSignatureDialogBase { - getRefactorAction().setEnabled(message == null); - getPreviewAction().setEnabled(message == null); - }); + getRefactorAction().setEnabled(message == null); + getPreviewAction().setEnabled(message == null); if (message != null) return new ValidationInfo(message); return super.doValidate(); } - @Override - public JComponent getPreferredFocusedComponent() { - return myNameField; - } - @Override protected String calculateSignature() { - @NonNls StringBuilder builder = new StringBuilder(); + final StringBuilder builder = new StringBuilder(); builder.append(getMethodName()); builder.append("("); final List parameters = myParametersTableModel.getItems(); - for (int i = 0; i != parameters.size(); ++i) { - PyParameterTableModelItem parameterInfo = parameters.get(i); + for (int i = 0; i < parameters.size(); i++) { + final PyParameterTableModelItem parameterInfo = parameters.get(i); builder.append(parameterInfo.parameter.getName()); final String defaultValue = parameterInfo.defaultValueCodeFragment.getText(); if (!defaultValue.isEmpty() && parameterInfo.isDefaultInSignature()) { - builder.append(" = " + defaultValue); + builder.append(" = ").append(defaultValue); } - if (i != parameters.size()-1) + if (i != parameters.size() - 1) { builder.append(", "); + } } builder.append(")"); return builder.toString(); @@ -256,151 +250,164 @@ public class PyChangeSignatureDialog extends ChangeSignatureDialogBase createVisibilityControl() { - return new ComboBoxVisibilityPanel<>(new String[0]); + return new ComboBoxVisibilityPanel<>(ArrayUtil.EMPTY_STRING_ARRAY); } @Override - protected JComponent getRowPresentation(ParameterTableModelItemBase item, boolean selected, final boolean focused) { - String text = item.parameter.getName(); - final String defaultCallValue = item.defaultValueCodeFragment.getText(); - PyParameterTableModelItem pyItem = (PyParameterTableModelItem)item; - final String defaultValue = pyItem.isDefaultInSignature()? pyItem.defaultValueCodeFragment.getText() : ""; - - if (StringUtil.isNotEmpty(defaultValue)) { - text += " = " + defaultValue; - } - - String tail = ""; - if (StringUtil.isNotEmpty(defaultCallValue)) { - tail += " default value = " + defaultCallValue; - } - if (!StringUtil.isEmpty(tail)) { - text += " //" + tail; - } - return JBListTable.createEditorTextFieldPresentation(getProject(), getFileType(), " " + text, selected, focused); - } - - @Override - protected boolean isListTableViewSupported() { - return true; - } - - @Override - protected JBTableRowEditor getTableEditor(final JTable t, final ParameterTableModelItemBase item) { - return new JBTableRowEditor() { - private EditorTextField myNameEditor; - private EditorTextField myDefaultValueEditor; - private JCheckBox myDefaultInSignature; - + protected ParametersListTable createParametersListTable() { + return new ParametersListTable() { @Override - public void prepareEditor(JTable table, int row) { - setLayout(new GridLayout(1, 3)); - final JPanel parameterPanel = createParameterPanel(); - add(parameterPanel); - final JPanel defaultValuePanel = createDefaultValuePanel(); - add(defaultValuePanel); - final JPanel defaultValueCheckBox = createDefaultValueCheckBox(); - add(defaultValueCheckBox); - - final String nameText = myNameEditor.getText(); - myDefaultValueEditor.setEnabled(!nameText.startsWith("*") - && !PyNames.CANONICAL_SELF.equals(nameText)); - myDefaultInSignature.setEnabled(!nameText.startsWith("*") - && !PyNames.CANONICAL_SELF.equals(nameText)); - } - - private JPanel createDefaultValueCheckBox() { - final JPanel defaultValuePanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 4, 2, true, false)); - - final JBLabel inSignatureLabel = new JBLabel(PyBundle.message("refactoring.change.signature.dialog.default.value.checkbox"), - UIUtil.ComponentStyle.SMALL); - IJSwingUtilities.adjustComponentsOnMac(inSignatureLabel, - myDefaultInSignature); - defaultValuePanel.add(inSignatureLabel, BorderLayout.WEST); - myDefaultInSignature = new JCheckBox(); - myDefaultInSignature.setSelected( - ((PyParameterTableModelItem)item).isDefaultInSignature()); - myDefaultInSignature.addItemListener(new ItemListener() { + protected JBTableRowRenderer getRowRenderer(int row) { + return new EditorTextFieldJBTableRowRenderer(getProject(), getFileType(), getDisposable()) { @Override - public void itemStateChanged(ItemEvent event) { - ((PyParameterTableModelItem)item) - .setDefaultInSignature(myDefaultInSignature.isSelected()); - } - }); - myDefaultInSignature.addChangeListener(mySignatureUpdater); - myDefaultInSignature.setEnabled(item.parameter.getOldIndex() == -1); - defaultValuePanel.add(myDefaultInSignature, BorderLayout.EAST); - return defaultValuePanel; - } + protected String getText(JTable table, int row) { + final PyParameterTableModelItem pyItem = getRowItem(row); + final StringBuilder text = new StringBuilder(pyItem.parameter.getName()); + final String defaultCallValue = pyItem.defaultValueCodeFragment.getText(); + final String defaultValue = pyItem.isDefaultInSignature() ? pyItem.defaultValueCodeFragment.getText() : ""; - private JPanel createDefaultValuePanel() { - final JPanel defaultValuePanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 4, 2, true, false)); - final Document doc = PsiDocumentManager.getInstance(getProject()).getDocument(item.defaultValueCodeFragment); - myDefaultValueEditor = new EditorTextField(doc, getProject(), getFileType()); - final JBLabel defaultValueLabel = new JBLabel(PyBundle.message("refactoring.change.signature.dialog.default.value.label"), - UIUtil.ComponentStyle.SMALL); - IJSwingUtilities.adjustComponentsOnMac(defaultValueLabel, myDefaultValueEditor); - defaultValuePanel.add(defaultValueLabel); - defaultValuePanel.add(myDefaultValueEditor); - myDefaultValueEditor.setPreferredWidth(t.getWidth() / 2); - myDefaultValueEditor.addDocumentListener(mySignatureUpdater); - return defaultValuePanel; - } - - private JPanel createParameterPanel() { - final JPanel namePanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 4, 2, true, false)); - myNameEditor = new EditorTextField(item.parameter.getName(), getProject(), getFileType()); - final JBLabel nameLabel = new JBLabel(PyBundle.message("refactoring.change.signature.dialog.name.label"), - UIUtil.ComponentStyle.SMALL); - IJSwingUtilities.adjustComponentsOnMac(nameLabel, myNameEditor); - namePanel.add(nameLabel); - namePanel.add(myNameEditor); - myNameEditor.setPreferredWidth(t.getWidth() / 2); - myNameEditor.addDocumentListener(new DocumentListener() { - @Override - public void documentChanged(DocumentEvent event) { - fireDocumentChanged(event, 0); - myDefaultValueEditor.setEnabled(!myNameEditor.getText().startsWith("*")); - myDefaultInSignature.setEnabled(!myNameEditor.getText().startsWith("*")); - } - }); - - myNameEditor.addDocumentListener(mySignatureUpdater); - return namePanel; - } - - @Override - public JBTableRow getValue() { - return new JBTableRow() { - @Override - public Object getValueAt(int column) { - switch (column) { - case 0: return myNameEditor.getText().trim(); - case 1: return new Pair<>(item.defaultValueCodeFragment, - ((PyParameterTableModelItem)item).isDefaultInSignature()); + if (StringUtil.isNotEmpty(defaultValue)) { + text.append(" = ").append(defaultValue); } - return null; + + if (StringUtil.isNotEmpty(defaultCallValue)) { + text.append(" // default value = ").append(defaultCallValue); + } + return text.toString(); + } + }; + } + + @NotNull + @Override + protected JBTableRowEditor getRowEditor(ParameterTableModelItemBase item) { + return new JBTableRowEditor() { + private EditorTextField myNameEditor; + private EditorTextField myDefaultValueEditor; + private JCheckBox myDefaultInSignature; + + @Override + public void prepareEditor(JTable table, int row) { + setLayout(new GridLayout(1, 3)); + final JPanel parameterPanel = createParameterPanel(); + add(parameterPanel); + final JPanel defaultValuePanel = createDefaultValuePanel(); + add(defaultValuePanel); + final JPanel defaultValueCheckBox = createDefaultValueCheckBox(); + add(defaultValueCheckBox); + + final String nameText = myNameEditor.getText(); + myDefaultValueEditor.setEnabled(!nameText.startsWith("*") && !PyNames.CANONICAL_SELF.equals(nameText)); + myDefaultInSignature.setEnabled(!nameText.startsWith("*") && !PyNames.CANONICAL_SELF.equals(nameText)); + } + + private JPanel createDefaultValueCheckBox() { + final JPanel defaultValuePanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 4, 2, true, false)); + + final JBLabel inSignatureLabel = new JBLabel(PyBundle.message("refactoring.change.signature.dialog.default.value.checkbox"), + UIUtil.ComponentStyle.SMALL); + IJSwingUtilities.adjustComponentsOnMac(inSignatureLabel, + myDefaultInSignature); + defaultValuePanel.add(inSignatureLabel, BorderLayout.WEST); + myDefaultInSignature = new JCheckBox(); + myDefaultInSignature.setSelected(((PyParameterTableModelItem)item).isDefaultInSignature()); + myDefaultInSignature.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent event) { + ((PyParameterTableModelItem)item).setDefaultInSignature(myDefaultInSignature.isSelected()); + } + }); + myDefaultInSignature.addChangeListener(mySignatureUpdater); + myDefaultInSignature.setEnabled(item.parameter.getOldIndex() == -1); + defaultValuePanel.add(myDefaultInSignature, BorderLayout.EAST); + return defaultValuePanel; + } + + private JPanel createDefaultValuePanel() { + final JPanel defaultValuePanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 4, 2, true, false)); + final Document doc = PsiDocumentManager.getInstance(getProject()).getDocument(item.defaultValueCodeFragment); + myDefaultValueEditor = new EditorTextField(doc, getProject(), getFileType()); + final JBLabel defaultValueLabel = new JBLabel(PyBundle.message("refactoring.change.signature.dialog.default.value.label"), + UIUtil.ComponentStyle.SMALL); + IJSwingUtilities.adjustComponentsOnMac(defaultValueLabel, myDefaultValueEditor); + defaultValuePanel.add(defaultValueLabel); + defaultValuePanel.add(myDefaultValueEditor); + myDefaultValueEditor.setPreferredWidth(getTable().getWidth() / 2); + myDefaultValueEditor.addDocumentListener(mySignatureUpdater); + return defaultValuePanel; + } + + private JPanel createParameterPanel() { + final JPanel namePanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 4, 2, true, false)); + myNameEditor = new EditorTextField(item.parameter.getName(), getProject(), getFileType()); + final JBLabel nameLabel = new JBLabel(PyBundle.message("refactoring.change.signature.dialog.name.label"), + UIUtil.ComponentStyle.SMALL); + IJSwingUtilities.adjustComponentsOnMac(nameLabel, myNameEditor); + namePanel.add(nameLabel); + namePanel.add(myNameEditor); + myNameEditor.setPreferredWidth(getTable().getWidth() / 2); + myNameEditor.addDocumentListener(new DocumentListener() { + @Override + public void documentChanged(DocumentEvent event) { + fireDocumentChanged(event, 0); + myDefaultValueEditor.setEnabled(!myNameEditor.getText().startsWith("*")); + myDefaultInSignature.setEnabled(!myNameEditor.getText().startsWith("*")); + } + }); + + myNameEditor.addDocumentListener(mySignatureUpdater); + return namePanel; + } + + @Override + public JBTableRow getValue() { + return new JBTableRow() { + @Override + public Object getValueAt(int column) { + switch (column) { + case 0: + return myNameEditor.getText().trim(); + case 1: + return new Pair<>(item.defaultValueCodeFragment, + ((PyParameterTableModelItem)item).isDefaultInSignature()); + } + return null; + } + }; + } + + @Override + public JComponent getPreferredFocusedComponent() { + return myNameEditor.getFocusTarget(); + } + + @Override + public JComponent[] getFocusableComponents() { + final List focusable = new ArrayList<>(); + focusable.add(myNameEditor.getFocusTarget()); + if (myDefaultValueEditor != null) { + focusable.add(myDefaultValueEditor.getFocusTarget()); + } + if (myDefaultInSignature != null) { + focusable.add(myDefaultInSignature); + } + return focusable.toArray(new JComponent[focusable.size()]); } }; } @Override - public JComponent getPreferredFocusedComponent() { - return myNameEditor.getFocusTarget(); - } - - @Override - public JComponent[] getFocusableComponents() { - final List focusable = new ArrayList<>(); - focusable.add(myNameEditor.getFocusTarget()); - if (myDefaultValueEditor != null) { - focusable.add(myDefaultValueEditor.getFocusTarget()); - } - return focusable.toArray(new JComponent[focusable.size()]); + protected boolean isRowEmpty(int row) { + return false; } }; } + @Override + protected boolean isListTableViewSupported() { + return true; + } + @Override protected boolean mayPropagateParameters() { return false; diff --git a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureHandler.java b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureHandler.java index 6b67be8045df..3fac20e8f0b4 100644 --- a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureHandler.java +++ b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureHandler.java @@ -139,7 +139,7 @@ public class PyChangeSignatureHandler implements ChangeSignatureHandler { CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, "refactoring.renameRefactorings"); } - private static boolean isNotUnderSourceRoot(@NotNull final Project project, @Nullable final PsiFile psiFile) { + public static boolean isNotUnderSourceRoot(@NotNull final Project project, @Nullable final PsiFile psiFile) { if (psiFile == null) { return true; } diff --git a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureProcessor.java b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureProcessor.java index 7dc010b07233..9b19d26d9577 100644 --- a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureProcessor.java +++ b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureProcessor.java @@ -28,10 +28,10 @@ import org.jetbrains.annotations.NotNull; public class PyChangeSignatureProcessor extends ChangeSignatureProcessorBase { - protected PyChangeSignatureProcessor(Project project, - PyFunction method, - String newName, - PyParameterInfo[] parameterInfo) { + public PyChangeSignatureProcessor(Project project, + PyFunction method, + String newName, + PyParameterInfo[] parameterInfo) { super(project, generateChangeInfo(method, newName, parameterInfo)); } diff --git a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureUsageProcessor.java b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureUsageProcessor.java index 2e7a96175f84..e84de500a694 100644 --- a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureUsageProcessor.java +++ b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureUsageProcessor.java @@ -34,6 +34,7 @@ import com.intellij.util.containers.MultiMap; import com.jetbrains.python.PyNames; import com.jetbrains.python.PythonLanguage; import com.jetbrains.python.documentation.docstrings.PyDocstringGenerator; +import com.jetbrains.python.inspections.quickfix.PyChangeSignatureQuickFix; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.search.PyOverridingMethodsSearch; import com.jetbrains.python.refactoring.PyRefactoringUtil; @@ -101,8 +102,13 @@ public class PyChangeSignatureUsageProcessor implements ChangeSignatureUsageProc RenameUtil.doRenameGenericNamedElement(method, changeInfo.getNewName(), usages, null); } if (element == null) return false; + if (element.getParent() instanceof PyCallExpression) { final PyCallExpression call = (PyCallExpression)element.getParent(); + // Don't modify the call that was the cause of Change Signature invocation + if (call.getUserData(PyChangeSignatureQuickFix.CHANGE_SIGNATURE_ORIGINAL_CALL) != null) { + return true; + } final PyArgumentList argumentList = call.getArgumentList(); if (argumentList != null) { final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(element.getProject()); diff --git a/python/testData/debug/test_series.py b/python/testData/debug/test_series.py new file mode 100644 index 000000000000..151c1211927d --- /dev/null +++ b/python/testData/debug/test_series.py @@ -0,0 +1,8 @@ +import pandas as pd +import numpy as np + +frame = pd.DataFrame(data=np.random.randint(0, high=10, size=(4, 2)), columns=['a', 'b'], index=pd.MultiIndex([['s', 'd'], [2, 3]], [[0, 0, 1, 1], [0, 1, 0, 1]])) + +series = frame.a + +print(series) # line 7 diff --git a/python/testData/inspections/ChangeSignatureAddKeywordOnlyParameter.py b/python/testData/inspections/ChangeSignatureAddKeywordOnlyParameter.py new file mode 100644 index 000000000000..86ecc3f8fd07 --- /dev/null +++ b/python/testData/inspections/ChangeSignatureAddKeywordOnlyParameter.py @@ -0,0 +1,4 @@ +def func(x, *args, foo=None): + pass + +func(1, 2, 3, bar='spam') \ No newline at end of file diff --git a/python/testData/inspections/ChangeSignatureAddKeywordOnlyParameter_after.py b/python/testData/inspections/ChangeSignatureAddKeywordOnlyParameter_after.py new file mode 100644 index 000000000000..2196ab7ac340 --- /dev/null +++ b/python/testData/inspections/ChangeSignatureAddKeywordOnlyParameter_after.py @@ -0,0 +1,4 @@ +def func(x, *args, foo=None, bar='spam'): + pass + +func(1, 2, 3, bar='spam') \ No newline at end of file diff --git a/python/testData/inspections/ChangeSignatureKeywordAndPositionalParameters.py b/python/testData/inspections/ChangeSignatureKeywordAndPositionalParameters.py new file mode 100644 index 000000000000..d557fb3a02f5 --- /dev/null +++ b/python/testData/inspections/ChangeSignatureKeywordAndPositionalParameters.py @@ -0,0 +1,5 @@ +def f(x, foo=1): + pass + + +f(x, 42, bar='spam') \ No newline at end of file diff --git a/python/testData/inspections/ChangeSignatureKeywordAndPositionalParameters_after.py b/python/testData/inspections/ChangeSignatureKeywordAndPositionalParameters_after.py new file mode 100644 index 000000000000..1d9c05e5cf52 --- /dev/null +++ b/python/testData/inspections/ChangeSignatureKeywordAndPositionalParameters_after.py @@ -0,0 +1,5 @@ +def f(x, foo=1, bar='spam'): + pass + + +f(x, 42, bar='spam') \ No newline at end of file diff --git a/python/testData/inspections/ChangeSignatureNewParametersNames.py b/python/testData/inspections/ChangeSignatureNewParametersNames.py new file mode 100644 index 000000000000..1477278aa0db --- /dev/null +++ b/python/testData/inspections/ChangeSignatureNewParametersNames.py @@ -0,0 +1,5 @@ +def func(i1): + i2 = 'Spam' + + +func(1, 2, 42 or 'str', get_num()) \ No newline at end of file diff --git a/python/testData/inspections/ChangeSignatureNewParametersNames_after.py b/python/testData/inspections/ChangeSignatureNewParametersNames_after.py new file mode 100644 index 000000000000..8281e37a5ed5 --- /dev/null +++ b/python/testData/inspections/ChangeSignatureNewParametersNames_after.py @@ -0,0 +1,5 @@ +def func(i1, i, i3, num): + i2 = 'Spam' + + +func(1, 2, 42 or 'str', get_num()) \ No newline at end of file diff --git a/python/testData/inspections/ChangeSignatureParametersDefaultValues.py b/python/testData/inspections/ChangeSignatureParametersDefaultValues.py new file mode 100644 index 000000000000..eceee62d0ccc --- /dev/null +++ b/python/testData/inspections/ChangeSignatureParametersDefaultValues.py @@ -0,0 +1,6 @@ +def func(): + pass + + +func(42, foo='spam') +func() diff --git a/python/testData/inspections/ChangeSignatureParametersDefaultValues_after.py b/python/testData/inspections/ChangeSignatureParametersDefaultValues_after.py new file mode 100644 index 000000000000..046a292bc48c --- /dev/null +++ b/python/testData/inspections/ChangeSignatureParametersDefaultValues_after.py @@ -0,0 +1,6 @@ +def func(i, foo='spam'): + pass + + +func(42, foo='spam') +func(42) diff --git a/python/testData/inspections/PyArgumentListInspection/badarglist.py b/python/testData/inspections/PyArgumentListInspection/badarglist.py index 8ac2755a27aa..08bf20f42b00 100644 --- a/python/testData/inspections/PyArgumentListInspection/badarglist.py +++ b/python/testData/inspections/PyArgumentListInspection/badarglist.py @@ -18,8 +18,8 @@ def f1(): pass f1() -f1(1) -f1(a = 1) +f1(1) +f1(a = 1) def f2(a): @@ -28,19 +28,19 @@ def f2(a): f2() # ok, fail f2(1) # ok, pass -f2(1, 2) # ok, fail +f2(1, 2) # ok, fail f2(a = 1) # ok, pass f2(b = 1) # ok, fail -f2(a = 1, b = 2) # ok, fail +f2(a = 1, b = 2) # ok, fail def f3(a, b): pass f3(1, 2) -f3(1, 2, 3) +f3(1, 2, 3) f3(b=2, a=1) -f3(b=1, b=2, a=1) +f3(b=1, b=2, a=1) f3(1, b=2) f3(a=1, 2) diff --git a/python/testData/inspections/PyArgumentListInspection/decorators.py b/python/testData/inspections/PyArgumentListInspection/decorators.py index 9377b5eb8148..6cb33c5d4842 100644 --- a/python/testData/inspections/PyArgumentListInspection/decorators.py +++ b/python/testData/inspections/PyArgumentListInspection/decorators.py @@ -13,7 +13,7 @@ def f7(): def f8(): pass -@deco(1, 2) # fail: extra param +@deco(1, 2) # fail: extra param def f9(): pass @@ -57,7 +57,7 @@ class Dec2: def __init__(self, p1, p2): pass -@Dec2() # fail: no p1, p2 +@Dec2() # fail: no p1, p2 def f17(): pass diff --git a/python/testData/inspections/PyArgumentListInspection/kwargsMapToNothing.py b/python/testData/inspections/PyArgumentListInspection/kwargsMapToNothing.py index 5029c3b56bd8..c285a60cf423 100644 --- a/python/testData/inspections/PyArgumentListInspection/kwargsMapToNothing.py +++ b/python/testData/inspections/PyArgumentListInspection/kwargsMapToNothing.py @@ -5,5 +5,5 @@ arg = [1, 2, 3] kwarg = {'c':3} f5(*arg, **kwarg) # ok f5(1,2, **kwarg) # ok -f5(1, 2, 3, **kwarg) # fail -f5(1, 2, 3, *arg) # fail +f5(1, 2, 3, **kwarg) # fail +f5(1, 2, 3, *arg) # fail diff --git a/python/testData/inspections/PyArgumentListInspection/py1268.py b/python/testData/inspections/PyArgumentListInspection/py1268.py index baaa9fd9e496..951df27428ee 100644 --- a/python/testData/inspections/PyArgumentListInspection/py1268.py +++ b/python/testData/inspections/PyArgumentListInspection/py1268.py @@ -3,8 +3,8 @@ def f(a, b, c): f(c=1, *(10, 20)) f(*(10, 20), c=1) -f(*(10, 20, 30), c=1) # fail: duplicate c -f(1, *(10, 20, 30)) # fail: tuple too long +f(*(10, 20, 30), c=1) # fail: duplicate c +f(1, *(10, 20, 30)) # fail: tuple too long f(1, *(10)) # fail: wrong type f(1, *(10,)) # fail: tuple too short, c not mapped @@ -24,14 +24,14 @@ f2(*(1,2), a=1, b=2, *(1,2)) # fail: a and b twice -f3(1, 2, *(3,), c=4) # fail: c twice +f3(c=3, a=1, b=2, *(1,2)) # fail: a and b twice +f3(1, 2, *(3,), c=4) # fail: c twice f3(1,2,3, *(1,2)) f3(c=3, *(1,2)) # -f3(1, c=3, *(1,2)) # fail: c twice -f3(c=3, a=1, b=2, d=(1,2)) # fail: unexpected d +f3(1, c=3, *(1,2)) # fail: c twice +f3(c=3, a=1, b=2, d=(1,2)) # fail: unexpected d f3(1, c=3, *(10,)) # ZZZ f3(1, *(10,)) f3(1, *(10,), c=20) f3(*(1,2), c=20) -f3(*(1,2), a=20) # fail: a twice +f3(*(1,2), a=20) # fail: a twice diff --git a/python/testData/inspections/PyArgumentListInspection/py3k.py b/python/testData/inspections/PyArgumentListInspection/py3k.py index 70e7820379f0..4ac87031c611 100644 --- a/python/testData/inspections/PyArgumentListInspection/py3k.py +++ b/python/testData/inspections/PyArgumentListInspection/py3k.py @@ -19,7 +19,7 @@ def a23(a, *b, c=1): pass a23(1,2,3, c=10) # pass -a23(1,2,3, c=10, a=1) # fail +a23(1,2,3, c=10, a=1) # fail a23(c=10, a=1) # pass a23(c=10, 1) # fail a23(*args, c=1) # pass diff --git a/python/testData/inspections/PyArgumentListInspection/tupleVsLiteralList.py b/python/testData/inspections/PyArgumentListInspection/tupleVsLiteralList.py index d3c41a99bd97..3936d0df1780 100644 --- a/python/testData/inspections/PyArgumentListInspection/tupleVsLiteralList.py +++ b/python/testData/inspections/PyArgumentListInspection/tupleVsLiteralList.py @@ -2,4 +2,4 @@ def f20(a, (b, c)): pass f20(1, [2, 3]) # ok -f20(1, (2, 3, 4)) # fail +f20(1, (2, 3, 4)) # fail diff --git a/python/testData/inspections/PyTypeCheckerInspection/TypingNamedTupleAsParameter.py b/python/testData/inspections/PyTypeCheckerInspection/TypingNamedTupleAsParameter.py new file mode 100644 index 000000000000..c638e5985517 --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/TypingNamedTupleAsParameter.py @@ -0,0 +1,12 @@ +from typing import NamedTuple + + +nt = NamedTuple("name", [("field", str)]) + + +def foo(x: nt): + pass + + +foo(5) +foo(nt(field = "f")) \ No newline at end of file diff --git a/python/testData/quickFixes/PyRemoveArgumentQuickFixTest/duplicate.py b/python/testData/quickFixes/PyRemoveArgumentQuickFixTest/duplicate.py index 4716f42c8564..47b5067cc548 100644 --- a/python/testData/quickFixes/PyRemoveArgumentQuickFixTest/duplicate.py +++ b/python/testData/quickFixes/PyRemoveArgumentQuickFixTest/duplicate.py @@ -2,4 +2,4 @@ def foo(a, p): pass -foo(1, p=2, p=33) +foo(1, p=2, p=33) diff --git a/python/testData/quickFixes/PyRemoveArgumentQuickFixTest/unexpected.py b/python/testData/quickFixes/PyRemoveArgumentQuickFixTest/unexpected.py index 82f42d31e15a..2ae09ef56bce 100644 --- a/python/testData/quickFixes/PyRemoveArgumentQuickFixTest/unexpected.py +++ b/python/testData/quickFixes/PyRemoveArgumentQuickFixTest/unexpected.py @@ -2,6 +2,6 @@ def foo(a): pass -foo(1, 23) +foo(1, 23) diff --git a/python/testData/stubs/FullyQualifiedTypingNamedTuple.py b/python/testData/stubs/FullyQualifiedTypingNamedTuple.py new file mode 100644 index 000000000000..0a0c72972354 --- /dev/null +++ b/python/testData/stubs/FullyQualifiedTypingNamedTuple.py @@ -0,0 +1,3 @@ +import typing + +nt = typing.NamedTuple("name", [("field", str)]) \ No newline at end of file diff --git a/python/testData/stubs/FullyQualifiedTypingNamedTupleKwargs.py b/python/testData/stubs/FullyQualifiedTypingNamedTupleKwargs.py new file mode 100644 index 000000000000..b968fb3ec634 --- /dev/null +++ b/python/testData/stubs/FullyQualifiedTypingNamedTupleKwargs.py @@ -0,0 +1,3 @@ +import typing + +nt = typing.NamedTuple("name", field=str) \ No newline at end of file diff --git a/python/testData/stubs/FullyQualifiedTypingNamedTupleKwargsWithAs.py b/python/testData/stubs/FullyQualifiedTypingNamedTupleKwargsWithAs.py new file mode 100644 index 000000000000..2d9e52c41d7b --- /dev/null +++ b/python/testData/stubs/FullyQualifiedTypingNamedTupleKwargsWithAs.py @@ -0,0 +1,3 @@ +import typing as T + +nt = T.NamedTuple("name", field=str) \ No newline at end of file diff --git a/python/testData/stubs/FullyQualifiedTypingNamedTupleWithAs.py b/python/testData/stubs/FullyQualifiedTypingNamedTupleWithAs.py new file mode 100644 index 000000000000..98b805441fb2 --- /dev/null +++ b/python/testData/stubs/FullyQualifiedTypingNamedTupleWithAs.py @@ -0,0 +1,3 @@ +import typing as T + +nt = T.NamedTuple("name", [("field", str)]) \ No newline at end of file diff --git a/python/testData/stubs/ImportedTypingNamedTuple.py b/python/testData/stubs/ImportedTypingNamedTuple.py new file mode 100644 index 000000000000..f123cc049321 --- /dev/null +++ b/python/testData/stubs/ImportedTypingNamedTuple.py @@ -0,0 +1,3 @@ +from typing import NamedTuple + +nt = NamedTuple("name", [("field", str)]) \ No newline at end of file diff --git a/python/testData/stubs/ImportedTypingNamedTupleFields.py b/python/testData/stubs/ImportedTypingNamedTupleFields.py new file mode 100644 index 000000000000..649fa3cf576a --- /dev/null +++ b/python/testData/stubs/ImportedTypingNamedTupleFields.py @@ -0,0 +1,4 @@ +from typing import NamedTuple +from b import fields + +nt = NamedTuple("name", fields) \ No newline at end of file diff --git a/python/testData/stubs/ImportedTypingNamedTupleKwargs.py b/python/testData/stubs/ImportedTypingNamedTupleKwargs.py new file mode 100644 index 000000000000..7ee5acf92e5f --- /dev/null +++ b/python/testData/stubs/ImportedTypingNamedTupleKwargs.py @@ -0,0 +1,3 @@ +from typing import NamedTuple + +nt = NamedTuple("name", field=str) \ No newline at end of file diff --git a/python/testData/stubs/ImportedTypingNamedTupleKwargsName.py b/python/testData/stubs/ImportedTypingNamedTupleKwargsName.py new file mode 100644 index 000000000000..de907d47e64b --- /dev/null +++ b/python/testData/stubs/ImportedTypingNamedTupleKwargsName.py @@ -0,0 +1,4 @@ +from typing import NamedTuple +from b import name + +nt = NamedTuple(name, x=str, y=int) \ No newline at end of file diff --git a/python/testData/stubs/ImportedTypingNamedTupleKwargsWithAs.py b/python/testData/stubs/ImportedTypingNamedTupleKwargsWithAs.py new file mode 100644 index 000000000000..43824d8ccfba --- /dev/null +++ b/python/testData/stubs/ImportedTypingNamedTupleKwargsWithAs.py @@ -0,0 +1,3 @@ +from typing import NamedTuple as NT + +nt = NT("name", field=str) \ No newline at end of file diff --git a/python/testData/stubs/ImportedTypingNamedTupleName.py b/python/testData/stubs/ImportedTypingNamedTupleName.py new file mode 100644 index 000000000000..e0eb71d471bc --- /dev/null +++ b/python/testData/stubs/ImportedTypingNamedTupleName.py @@ -0,0 +1,4 @@ +from typing import NamedTuple +from b import name + +nt = NamedTuple(name, [("x", str), ("y", int)]) \ No newline at end of file diff --git a/python/testData/stubs/ImportedTypingNamedTupleWithAs.py b/python/testData/stubs/ImportedTypingNamedTupleWithAs.py new file mode 100644 index 000000000000..ef85b30618a3 --- /dev/null +++ b/python/testData/stubs/ImportedTypingNamedTupleWithAs.py @@ -0,0 +1,3 @@ +from typing import NamedTuple as NT + +nt = NT("name", [("field", str)]) \ No newline at end of file diff --git a/python/testData/stubs/TypingNamedTupleFieldsChain.py b/python/testData/stubs/TypingNamedTupleFieldsChain.py new file mode 100644 index 000000000000..08f97f5c22e1 --- /dev/null +++ b/python/testData/stubs/TypingNamedTupleFieldsChain.py @@ -0,0 +1,5 @@ +from typing import NamedTuple + +fields0 = [("x", str), ("y", int)] +fields = fields0 +nt = NamedTuple("name", fields) \ No newline at end of file diff --git a/python/testData/stubs/TypingNamedTupleFieldsReference.py b/python/testData/stubs/TypingNamedTupleFieldsReference.py new file mode 100644 index 000000000000..de9e4629f175 --- /dev/null +++ b/python/testData/stubs/TypingNamedTupleFieldsReference.py @@ -0,0 +1,4 @@ +from typing import NamedTuple + +fields = [("x", str), ("y", int)] +nt = NamedTuple("name", fields) \ No newline at end of file diff --git a/python/testData/stubs/TypingNamedTupleKwargsNameChain.py b/python/testData/stubs/TypingNamedTupleKwargsNameChain.py new file mode 100644 index 000000000000..af123e5b88b2 --- /dev/null +++ b/python/testData/stubs/TypingNamedTupleKwargsNameChain.py @@ -0,0 +1,5 @@ +from typing import NamedTuple + +name0 = "name" +name = name0 +nt = NamedTuple(name, x=str, y=int) \ No newline at end of file diff --git a/python/testData/stubs/TypingNamedTupleKwargsNameReference.py b/python/testData/stubs/TypingNamedTupleKwargsNameReference.py new file mode 100644 index 000000000000..781aedeb074c --- /dev/null +++ b/python/testData/stubs/TypingNamedTupleKwargsNameReference.py @@ -0,0 +1,4 @@ +from typing import NamedTuple + +name = "name" +nt = NamedTuple(name, x=str, y=int) \ No newline at end of file diff --git a/python/testData/stubs/TypingNamedTupleNameChain.py b/python/testData/stubs/TypingNamedTupleNameChain.py new file mode 100644 index 000000000000..338bc79f7077 --- /dev/null +++ b/python/testData/stubs/TypingNamedTupleNameChain.py @@ -0,0 +1,5 @@ +from typing import NamedTuple + +name0 = "name" +name = name0 +nt = NamedTuple(name, [("x", str), ("y", int)]) \ No newline at end of file diff --git a/python/testData/stubs/TypingNamedTupleNameReference.py b/python/testData/stubs/TypingNamedTupleNameReference.py new file mode 100644 index 000000000000..3437d9e5db67 --- /dev/null +++ b/python/testData/stubs/TypingNamedTupleNameReference.py @@ -0,0 +1,4 @@ +from typing import NamedTuple + +name = "name" +nt = NamedTuple(name, [("x", str), ("y", int)]) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/env/python/PythonDataViewerTest.java b/python/testSrc/com/jetbrains/env/python/PythonDataViewerTest.java index 7686c210bf46..81ffcd89ccad 100644 --- a/python/testSrc/com/jetbrains/env/python/PythonDataViewerTest.java +++ b/python/testSrc/com/jetbrains/env/python/PythonDataViewerTest.java @@ -20,6 +20,7 @@ import com.intellij.util.Consumer; import com.intellij.xdebugger.XDebugSession; import com.intellij.xdebugger.XDebuggerTestUtil; import com.jetbrains.env.PyEnvTestCase; +import com.jetbrains.env.Staging; import com.jetbrains.env.python.debug.PyDebuggerTask; import com.jetbrains.python.debugger.ArrayChunk; import com.jetbrains.python.debugger.PyDebugValue; @@ -75,6 +76,20 @@ public class PythonDataViewerTest extends PyEnvTestCase { }); } + @Test + @Staging + public void testSeries() throws Exception { + runPythonTest(new PyDataFrameDebuggerTask(getRelativeTestDataPath(), "test_series.py", ImmutableSet.of(7)) { + @Override + public void testing() throws Exception { + doTest("series", 4, 1, arrayChunk -> { + List labels = arrayChunk.getRowLabels(); + assertSameElements(labels, "s/2", "s/3", "d/2", "d/3"); + }); + } + }); + } + private static class PyDataFrameDebuggerTask extends PyDebuggerTask { private Set myLines; diff --git a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java index 05f1b21b15f9..bf03e83e183d 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java @@ -17,8 +17,10 @@ package com.jetbrains.python; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInspection.ex.InspectionProfileImpl; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.testFramework.TestDataFile; import com.intellij.testFramework.TestDataPath; +import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.codeInsight.PyCodeInsightSettings; import com.jetbrains.python.documentation.docstrings.DocStringFormat; import com.jetbrains.python.fixtures.PyTestCase; @@ -647,6 +649,28 @@ public class PyQuickFixTest extends PyTestCase { " \"((/(?P.+))?))\")"); } + // PY-8174 + public void testChangeSignatureKeywordAndPositionalParameters() { + doInspectionTest(PyArgumentListInspection.class, "Change signature of f(x, foo, bar)", true, true); + } + + // PY-8174 + public void testChangeSignatureAddKeywordOnlyParameter() { + runWithLanguageLevel(LanguageLevel.PYTHON30, () -> { + doInspectionTest(PyArgumentListInspection.class, "Change signature of func(x, *args, foo, bar)", true, true); + }); + } + + // PY-8174 + public void testChangeSignatureNewParametersNames() { + doInspectionTest(PyArgumentListInspection.class, "Change signature of func(i1, i, i3, num)", true, true); + } + + // PY-8174 + public void testChangeSignatureParametersDefaultValues() { + doInspectionTest(PyArgumentListInspection.class, "Change signature of func(i, foo)", true, true); + } + @Override @NonNls protected String getTestDataPath() { @@ -690,7 +714,9 @@ public class PyQuickFixTest extends PyTestCase { final List intentionActions = myFixture.filterAvailableIntentions(quickFixName); if (available) { if (intentionActions.isEmpty()) { - throw new AssertionError("Quickfix \"" + quickFixName + "\" is not available"); + final List intentionNames = ContainerUtil.map(myFixture.getAvailableIntentions(), IntentionAction::getText); + throw new AssertionError("Quickfix starting with \"" + quickFixName + "\" is not available. " + + "Available intentions:\n" + StringUtil.join(intentionNames, "\n")); } if (intentionActions.size() > 1) { throw new AssertionError("There are more than one quickfix with the name \"" + quickFixName + "\""); diff --git a/python/testSrc/com/jetbrains/python/PyStubsTest.java b/python/testSrc/com/jetbrains/python/PyStubsTest.java index d6b65743325c..5a055a1fe49c 100644 --- a/python/testSrc/com/jetbrains/python/PyStubsTest.java +++ b/python/testSrc/com/jetbrains/python/PyStubsTest.java @@ -490,6 +490,94 @@ public class PyStubsTest extends PyTestCase { doTestUnsupportedNamedTuple(); } + public void testFullyQualifiedTypingNamedTuple() { + doTestNamedTuple( + QualifiedName.fromDottedString("typing.NamedTuple") + ); + } + + public void testFullyQualifiedTypingNamedTupleWithAs() { + doTestNamedTuple( + QualifiedName.fromDottedString("T.NamedTuple") + ); + } + + public void testImportedTypingNamedTuple() { + doTestNamedTuple( + QualifiedName.fromComponents("NamedTuple") + ); + } + + public void testImportedTypingNamedTupleWithAs() { + doTestNamedTuple( + QualifiedName.fromComponents("NT") + ); + } + + public void testTypingNamedTupleNameReference() { + doTestNamedTypingTupleArguments(); + } + + public void testTypingNamedTupleFieldsReference() { + doTestNamedTypingTupleArguments(); + } + + public void testTypingNamedTupleNameChain() { + doTestNamedTypingTupleArguments(); + } + + public void testTypingNamedTupleFieldsChain() { + doTestNamedTypingTupleArguments(); + } + + public void _testImportedTypingNamedTupleName() { + doTestUnsupportedNamedTuple(); + } + + public void _testImportedTypingNamedTupleFields() { + doTestUnsupportedNamedTuple(); + } + + public void testFullyQualifiedTypingNamedTupleKwargs() { + doTestNamedTuple( + QualifiedName.fromDottedString("typing.NamedTuple") + ); + } + + public void testFullyQualifiedTypingNamedTupleKwargsWithAs() { + doTestNamedTuple( + QualifiedName.fromDottedString("T.NamedTuple") + ); + } + + public void testImportedTypingNamedTupleKwargs() { + doTestNamedTuple( + QualifiedName.fromComponents("NamedTuple") + ); + } + + public void testImportedTypingNamedTupleKwargsWithAs() { + doTestNamedTuple( + QualifiedName.fromComponents("NT") + ); + } + + public void testTypingNamedTupleKwargsNameReference() { + doTestNamedTypingTupleArguments(); + } + + public void testTypingNamedTupleKwargsNameChain() { + doTestNamedTypingTupleArguments(); + } + + public void _testImportedTypingNamedTupleKwargsName() { + doTestUnsupportedNamedTuple(); + } + + public void _testImportedTypingNamedTupleKwargsFields() { + doTestUnsupportedNamedTuple(); + } + private void doTestNamedTuple(@NotNull QualifiedName expectedCalleeName) { doTestNamedTuple("name", Collections.singletonList("field"), expectedCalleeName); } @@ -498,6 +586,10 @@ public class PyStubsTest extends PyTestCase { doTestNamedTuple("name", Arrays.asList("x", "y"), QualifiedName.fromComponents("namedtuple")); } + private void doTestNamedTypingTupleArguments() { + doTestNamedTuple("name", Arrays.asList("x", "y"), QualifiedName.fromComponents("NamedTuple")); + } + private void doTestNamedTuple(@NotNull String expectedName, @NotNull List expectedFields, @NotNull QualifiedName expectedCalleeName) { diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index 85fa183469b9..ddfea706620b 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -1677,6 +1677,18 @@ public class PyTypeTest extends PyTestCase { " return get_class()"); } + // PY-7322 + public void testNamedTupleParameterInDocString() { + doTest("Point", + "from collections import namedtuple\n" + + "Point = namedtuple('Point', ('x', 'y'))\n" + + "def takes_a_point(point):\n" + + " \"\"\"\n" + + " :type point: Point\n" + + " \"\"\"\n" + + " expr = point"); + } + // PY-22919 public void testMaxListKnownElements() { doTest("int", diff --git a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java index 480437f318ce..7496bec64430 100644 --- a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java +++ b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1136,6 +1136,62 @@ public class PythonCompletionTest extends PyTestCase { assertDoesntContain(suggested, "_T", "_KT"); } + // PY-18246 + public void testTypingNamedTupleCreatedViaCallInstance() { + myFixture.copyDirectoryToProject("../typing", ""); + + final List suggested = doTestByText( + "from typing import NamedTuple\n" + + "EmployeeRecord = NamedTuple('EmployeeRecord', [\n" + + " ('name', str),\n" + + " ('age', int),\n" + + " ('title', str),\n" + + " ('department', str)\n" + + "])\n" + + "e = EmployeeRecord('n', 'a', 't', 'd')\n" + + "e." + ); + assertNotNull(suggested); + assertContainsElements(suggested, "name", "age", "title", "department"); + } + + // PY-18246 + public void testTypingNamedTupleCreatedViaKwargsCallInstance() { + myFixture.copyDirectoryToProject("../typing", ""); + + final List suggested = doTestByText( + "from typing import NamedTuple\n" + + "EmployeeRecord = NamedTuple('EmployeeRecord', name=str, age=int, title=str, department=str)\n" + + "e = EmployeeRecord('n', 'a', 't', 'd')\n" + + "e." + ); + assertNotNull(suggested); + assertContainsElements(suggested, "name", "age", "title", "department"); + } + + // PY-18246 + public void testTypingNamedTupleCreatedViaInheritanceInstance() { + runWithLanguageLevel( + LanguageLevel.PYTHON36, + () -> { + myFixture.copyDirectoryToProject("../typing", ""); + + final List suggested = doTestByText( + "from typing import NamedTuple\n" + + "class EmployeeRecord(NamedTuple):\n" + + " name: str\n" + + " age: int\n" + + " title: str\n" + + " department: str\n" + + "e = EmployeeRecord('n', 'a', 't', 'd')\n" + + "e." + ); + assertNotNull(suggested); + assertContainsElements(suggested, "name", "age", "title", "department"); + } + ); + } + // PY-21519 public void testTypeComment() { myFixture.copyFileToProject("../typing/typing.py"); diff --git a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java index cbaef8617e4e..c118d3effe03 100644 --- a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java @@ -233,4 +233,8 @@ public class Py3TypeCheckerInspectionTest extends PyTestCase { public void testGenericKwargs() { doTest(); } + + public void testTypingNamedTupleAsParameter() { + doTest(); + } } diff --git a/xml/impl/src/com/intellij/xml/breadcrumbs/BreadcrumbsInitializingActivity.java b/xml/impl/src/com/intellij/xml/breadcrumbs/BreadcrumbsInitializingActivity.java index 0058cbe3babe..418da0e5308f 100644 --- a/xml/impl/src/com/intellij/xml/breadcrumbs/BreadcrumbsInitializingActivity.java +++ b/xml/impl/src/com/intellij/xml/breadcrumbs/BreadcrumbsInitializingActivity.java @@ -41,7 +41,7 @@ import org.jetbrains.annotations.NotNull; public class BreadcrumbsInitializingActivity implements StartupActivity, DumbAware { @Override public void runActivity(@NotNull Project project) { - if (project.isDefault() || ApplicationManager.getApplication().isUnitTestMode()) { + if (project.isDefault() || ApplicationManager.getApplication().isUnitTestMode() || project.isDisposed()) { return; } diff --git a/xml/impl/src/com/intellij/xml/breadcrumbs/BreadcrumbsXmlWrapper.java b/xml/impl/src/com/intellij/xml/breadcrumbs/BreadcrumbsXmlWrapper.java index 372035d8794d..5f0d72f7af76 100644 --- a/xml/impl/src/com/intellij/xml/breadcrumbs/BreadcrumbsXmlWrapper.java +++ b/xml/impl/src/com/intellij/xml/breadcrumbs/BreadcrumbsXmlWrapper.java @@ -493,12 +493,16 @@ public class BreadcrumbsXmlWrapper extends JComponent implements Disposable { @Nullable private static BreadcrumbsProvider getInfoProvider(@NotNull Language language) { - for (BreadcrumbsProvider provider : BreadcrumbsProvider.EP_NAME.getExtensions()) { - for (Language supported : provider.getLanguages()) { - if (supported.isKindOf(language)) { - return provider; + BreadcrumbsProvider[] providers = BreadcrumbsProvider.EP_NAME.getExtensions(); + while (language != null) { + for (BreadcrumbsProvider provider : providers) { + for (Language supported : provider.getLanguages()) { + if (language.is(supported)) { + return provider; + } } } + language = language.getBaseLanguage(); } return null; } diff --git a/xml/xml-psi-impl/src/com/intellij/application/options/editor/CssFoldingSettings.java b/xml/xml-psi-impl/src/com/intellij/application/options/editor/CssFoldingSettings.java deleted file mode 100644 index 264eb995b2be..000000000000 --- a/xml/xml-psi-impl/src/com/intellij/application/options/editor/CssFoldingSettings.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2000-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.intellij.application.options.editor; - -import com.intellij.openapi.components.PersistentStateComponent; -import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.components.State; -import com.intellij.openapi.components.Storage; -import com.intellij.util.xmlb.XmlSerializerUtil; -import org.jetbrains.annotations.Nullable; - -/** - * User: zolotov - * Date: 4/18/13 - * @deprecated use {@link XmlFoldingSettings} - * todo: delete after 2017.1 release - */ -@State( - name="CssFoldingSettings", - storages= { - @Storage(value = "editor.codeinsight.xml", deprecated = true)} -) -public class CssFoldingSettings implements PersistentStateComponent { - public static CssFoldingSettings getInstance() { - return ServiceManager.getService(CssFoldingSettings.class); - } - - private boolean myCollapseDataUri = true; - - public boolean isCollapseDataUri() { - return myCollapseDataUri; - } - - @SuppressWarnings("UnusedDeclaration") - public void setCollapseDataUri(boolean value) { - myCollapseDataUri = value; - } - - @Nullable - @Override - public CssFoldingSettings getState() { - return this; - } - - @Override - public void loadState(CssFoldingSettings state) { - XmlSerializerUtil.copyBean(state, this); - } -} diff --git a/xml/xml-psi-impl/src/com/intellij/application/options/editor/XmlFoldingSettings.java b/xml/xml-psi-impl/src/com/intellij/application/options/editor/XmlFoldingSettings.java index 54e8be90eef2..96873f18e36b 100644 --- a/xml/xml-psi-impl/src/com/intellij/application/options/editor/XmlFoldingSettings.java +++ b/xml/xml-psi-impl/src/com/intellij/application/options/editor/XmlFoldingSettings.java @@ -31,14 +31,6 @@ public class XmlFoldingSettings implements XmlCodeFoldingSettings, PersistentSta return ServiceManager.getService(XmlFoldingSettings.class); } - public XmlFoldingSettings() { - // todo: remove after 2017.1 release - CssFoldingSettings cssFoldingSettings = CssFoldingSettings.getInstance(); - if (cssFoldingSettings != null) { - myState.COLLAPSE_DATA_URI = cssFoldingSettings.isCollapseDataUri(); - } - } - @Override public boolean isCollapseXmlTags() { return myState.COLLAPSE_XML_TAGS;