From 550975842ea4fe14a974088d2262fb04b588806d Mon Sep 17 00:00:00 2001 From: "Ilya.Kazakevich" Date: Fri, 10 Oct 2014 19:35:35 +0400 Subject: [PATCH] PY-13180 Pycharm 3.4: Right mouse click on selected text inside unicode string hangs out IDE interface for 20-30 seconds PY-13983 PyCharm hangs (~100% CPU) when right clicking highlighted text TODO: Add tests (performance and modification) --- .../com/intellij/psi/util/PsiCacheKey.java | 67 +++++++++++++++++-- .../nameResolver/NameResolverTools.java | 65 ++++++++++++------ .../magicLiteral/PyMagicLiteralTools.java | 33 +++++++-- 3 files changed, 132 insertions(+), 33 deletions(-) diff --git a/platform/core-api/src/com/intellij/psi/util/PsiCacheKey.java b/platform/core-api/src/com/intellij/psi/util/PsiCacheKey.java index 1f31645ed258..cdaf8d067426 100644 --- a/platform/core-api/src/com/intellij/psi/util/PsiCacheKey.java +++ b/platform/core-api/src/com/intellij/psi/util/PsiCacheKey.java @@ -28,12 +28,18 @@ import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class PsiCacheKey extends Key>> { - private final Function myFunction; +public class PsiCacheKey extends Key>> { + private final Function myFunction; + /** + * One of {@link com.intellij.psi.util.PsiModificationTracker} constants that marks when to flush cache + */ + @NotNull + private final Key myModifyCause; - private PsiCacheKey(@NonNls @NotNull String name, @NotNull Function function) { + private PsiCacheKey(@NonNls @NotNull String name, @NotNull Function function, @NotNull Key modifyCause) { super(name); myFunction = function; + myModifyCause = modifyCause; } public final T getValue(@NotNull H h) { @@ -43,7 +49,7 @@ public class PsiCacheKey extends Key>(new Pair(count, result))); return result; } @@ -52,14 +58,61 @@ public class PsiCacheKey extends Key> ref = h.getUserData(this); Pair data = SoftReference.dereference(ref); - if (data == null || data.getFirst() != h.getManager().getModificationTracker().getJavaStructureModificationCount()) { + if (data == null || data.getFirst() != getModificationCount(h.getManager().getModificationTracker())) { return null; } return data.getSecond(); } - public static PsiCacheKey create(@NonNls @NotNull String name, @NotNull Function function) { - return new PsiCacheKey(name, function); + + /** + * Gets modification count from tracker based on {@link #myModifyCause} + * + * @param tracker track to get modification count from + * @return modification count + * @throws AssertionError if {@link #myModifyCause} is junk + */ + private long getModificationCount(@NotNull PsiModificationTracker tracker) { + if (myModifyCause.equals(PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT)) { + return tracker.getJavaStructureModificationCount(); + } + if (myModifyCause.equals(PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT)) { + return tracker.getOutOfCodeBlockModificationCount(); + } + if (myModifyCause.equals(PsiModificationTracker.MODIFICATION_COUNT)) { + return tracker.getModificationCount(); + } + throw new AssertionError("No modification tracker found for key " + myModifyCause); + } + + /** + * Creates cache key value + * + * @param name key name + * @param function function to reproduce new value when old value is stale + * @param modifyCause one one {@link com.intellij.psi.util.PsiModificationTracker}'s constants that marks when to flush cache + * @param value type + * @param key type + * @return instance + */ + public static PsiCacheKey create(@NonNls @NotNull String name, + @NotNull Function function, + @NotNull Key modifyCause) { + return new PsiCacheKey(name, function, modifyCause); + } + + /** + * Creates cache key value using {@link com.intellij.psi.util.PsiModificationTracker#JAVA_STRUCTURE_MODIFICATION_COUNT} as + * modification count to flush cache + * + * @param name key name + * @param function function to reproduce new value when old value is stale + * @param value type + * @param key type + * @return instance + */ + public static PsiCacheKey create(@NonNls @NotNull String name, @NotNull Function function) { + return create(name, function, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT); } } diff --git a/python/psi-api/src/com/jetbrains/python/nameResolver/NameResolverTools.java b/python/psi-api/src/com/jetbrains/python/nameResolver/NameResolverTools.java index 7fad93339ad6..2817a60400d2 100644 --- a/python/psi-api/src/com/jetbrains/python/nameResolver/NameResolverTools.java +++ b/python/psi-api/src/com/jetbrains/python/nameResolver/NameResolverTools.java @@ -1,9 +1,13 @@ package com.jetbrains.python.nameResolver; import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiReference; +import com.intellij.psi.util.PsiCacheKey; +import com.intellij.psi.util.PsiModificationTracker; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.Function; import com.jetbrains.python.psi.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -16,6 +20,12 @@ import java.util.List; * @author Ilya.Kazakevich */ public final class NameResolverTools { + /** + * Cache: pair [qualified element name, class name (may be null)] by any psi element. + */ + private static final PsiCacheKey, PyElement> QUALIFIED_AND_CLASS_NAME = + PsiCacheKey.create(NameResolverTools.class.getName(), new QualifiedAndClassNameObtainer(), PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT); + private NameResolverTools() { } @@ -45,25 +55,9 @@ public final class NameResolverTools { * @return true if element's fqn is one of names, provided by provider */ public static boolean isName(@NotNull final PyElement element, @NotNull final FQNamesProvider... namesProviders) { - PyElement elementToCheck = element; - final PsiReference reference = element.getReference(); - if (reference != null) { - final PsiElement resolvedElement = reference.resolve(); - if (resolvedElement instanceof PyElement) { - elementToCheck = (PyElement)resolvedElement; - } - } - String qualifiedName = null; - if (elementToCheck instanceof PyQualifiedNameOwner) { - qualifiedName = ((PyQualifiedNameOwner)elementToCheck).getQualifiedName(); - } - String className = null; - if (elementToCheck instanceof PyFunction) { - final PyClass aClass = ((PyFunction)elementToCheck).getContainingClass(); - if (aClass != null) { - className = aClass.getQualifiedName(); - } - } + final Pair qualifiedAndClassName = QUALIFIED_AND_CLASS_NAME.getValue(element); + final String qualifiedName = qualifiedAndClassName.first; + final String className = qualifiedAndClassName.second; for (final FQNamesProvider provider : namesProviders) { final List names = Arrays.asList(provider.getNames()); @@ -79,7 +73,8 @@ public final class NameResolverTools { /** * Looks for parent call of certain function - * @param anchor element to look parent for + * + * @param anchor element to look parent for * @param functionName function to find * @return parent call or null if not found */ @@ -111,4 +106,34 @@ public final class NameResolverTools { return false; } } + + /** + * Returns pair [qualified name, class name (may be null)] by psi element + */ + private static class QualifiedAndClassNameObtainer implements Function> { + @Override + @NotNull + public Pair fun(@NotNull final PyElement element) { + PyElement elementToCheck = element; + final PsiReference reference = element.getReference(); + if (reference != null) { + final PsiElement resolvedElement = reference.resolve(); + if (resolvedElement instanceof PyElement) { + elementToCheck = (PyElement)resolvedElement; + } + } + String qualifiedName = null; + if (elementToCheck instanceof PyQualifiedNameOwner) { + qualifiedName = ((PyQualifiedNameOwner)elementToCheck).getQualifiedName(); + } + String className = null; + if (elementToCheck instanceof PyFunction) { + final PyClass aClass = ((PyFunction)elementToCheck).getContainingClass(); + if (aClass != null) { + className = aClass.getQualifiedName(); + } + } + return Pair.create(qualifiedName, className); + } + } } diff --git a/python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralTools.java b/python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralTools.java index d119ec0646dc..7cd2c052cf79 100644 --- a/python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralTools.java +++ b/python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralTools.java @@ -1,7 +1,11 @@ package com.jetbrains.python.magicLiteral; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiCacheKey; +import com.intellij.psi.util.PsiModificationTracker; +import com.intellij.util.Function; import com.jetbrains.python.psi.StringLiteralExpression; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -12,6 +16,13 @@ import org.jetbrains.annotations.Nullable; * @author Ilya.Kazakevich */ public final class PyMagicLiteralTools { + /** + * Cache: ref (value may be null or extension point) by by string literal + */ + private final static PsiCacheKey, StringLiteralExpression> MAGIC_LITERAL_POINT = + PsiCacheKey + .create(PyMagicLiteralTools.class.getName(), new MagicLiteralChecker(), PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT); + private PyMagicLiteralTools() { } @@ -34,14 +45,24 @@ public final class PyMagicLiteralTools { */ @Nullable public static PyMagicLiteralExtensionPoint getPoint(@NotNull final StringLiteralExpression element) { - final PyMagicLiteralExtensionPoint[] magicLiteralExtPoints = - ApplicationManager.getApplication().getExtensions(PyMagicLiteralExtensionPoint.EP_NAME); + return MAGIC_LITERAL_POINT.getValue(element).get(); + } - for (final PyMagicLiteralExtensionPoint magicLiteralExtensionPoint : magicLiteralExtPoints) { - if (magicLiteralExtensionPoint.isMagicLiteral(element)) { - return magicLiteralExtensionPoint; + /** + * Obtains ref (value may be null or extension point) by by string literal + */ + private static class MagicLiteralChecker implements Function> { + @Override + public Ref fun(StringLiteralExpression element) { + final PyMagicLiteralExtensionPoint[] magicLiteralExtPoints = + ApplicationManager.getApplication().getExtensions(PyMagicLiteralExtensionPoint.EP_NAME); + + for (final PyMagicLiteralExtensionPoint magicLiteralExtensionPoint : magicLiteralExtPoints) { + if (magicLiteralExtensionPoint.isMagicLiteral(element)) { + return Ref.create(magicLiteralExtensionPoint); + } } + return new Ref(); } - return null; } }