PY-13180 Pycharm 3.4: Right mouse click on selected text inside unicode string hangs out IDE interface for 20-30 seconds:

Performance fix #3
This commit is contained in:
Ilya.Kazakevich
2014-07-04 02:40:59 +04:00
parent bc5f5e3450
commit ab030032c7
7 changed files with 392 additions and 284 deletions
@@ -34,7 +34,7 @@ import org.jetbrains.annotations.Nullable;
*/
public class PyMagicLiteralRenameHandler implements RenameHandler {
@Override
public boolean isAvailableOnDataContext(DataContext dataContext) {
public boolean isAvailableOnDataContext(final DataContext dataContext) {
final Editor editor = CommonDataKeys.EDITOR.getData(dataContext);
if (editor == null) {
return false;
@@ -47,11 +47,7 @@ public class PyMagicLiteralRenameHandler implements RenameHandler {
final PsiElement element = getElement(file, editor);
if (element == null || !PyMagicLiteralTools.isMagicLiteral(element)) {
return false;
}
return true;
return !((element == null) || !PyMagicLiteralTools.isMagicLiteral(element));
}
@Nullable
@@ -1,277 +0,0 @@
/*
* Copyright 2000-2013 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;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.QualifiedName;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
*
* @author yole
*/
public class PyBlockEvaluator {
private final Map<String, Object> myNamespace = new HashMap<String, Object>();
private final Map<String, List<PyExpression>> myDeclarations = new HashMap<String, List<PyExpression>>();
private final Set<PyFile> myVisitedFiles;
private final Set<String> myDeclarationsToTrack = new HashSet<String>();
private String myCurrentFilePath;
private Object myReturnValue;
private boolean myEvaluateCollectionItems = true;
public PyBlockEvaluator() {
myVisitedFiles = new HashSet<PyFile>();
}
public PyBlockEvaluator(Set<PyFile> visitedFiles) {
myVisitedFiles = visitedFiles;
}
public void trackDeclarations(String attrName) {
myDeclarationsToTrack.add(attrName);
}
public void evaluate(PyElement element) {
VirtualFile vFile = element.getContainingFile().getVirtualFile();
myCurrentFilePath = vFile != null ? vFile.getPath() : null;
if (myVisitedFiles.contains(element)) {
return;
}
myVisitedFiles.add((PyFile)element.getContainingFile());
PyElement statementContainer = element instanceof PyFunction ? ((PyFunction) element).getStatementList() : element;
if (statementContainer == null) {
return;
}
statementContainer.acceptChildren(new PyElementVisitor() {
@Override
public void visitPyAssignmentStatement(PyAssignmentStatement node) {
PyExpression expression = node.getLeftHandSideExpression();
if (expression instanceof PyTargetExpression) {
String name = expression.getName();
PyExpression value = ((PyTargetExpression)expression).findAssignedValue();
myNamespace.put(name, prepareEvaluator().evaluate(value));
if (myDeclarationsToTrack.contains(name)) {
List<PyExpression> declarations = new ArrayList<PyExpression>();
PyPsiUtils.sequenceToList(declarations, value);
myDeclarations.put(name, declarations);
}
}
else if (expression instanceof PySubscriptionExpression) {
PyExpression operand = ((PySubscriptionExpression)expression).getOperand();
PyExpression indexExpression = ((PySubscriptionExpression)expression).getIndexExpression();
if (operand instanceof PyReferenceExpression && ((PyReferenceExpression)operand).getQualifier() == null) {
Object currentValue = myNamespace.get(((PyReferenceExpression)operand).getReferencedName());
if (currentValue instanceof Map) {
Object mapKey = prepareEvaluator().evaluate(indexExpression);
if (mapKey != null) {
Object value = myEvaluateCollectionItems ? prepareEvaluator().evaluate(node.getAssignedValue()) : node.getAssignedValue();
((Map)currentValue).put(mapKey, value);
}
}
}
}
}
@Override
public void visitPyAugAssignmentStatement(PyAugAssignmentStatement node) {
PyExpression target = node.getTarget();
String name = target.getName();
if (target instanceof PyReferenceExpression && !((PyReferenceExpression)target).isQualified() && name != null) {
Object currentValue = myNamespace.get(name);
if (currentValue != null) {
Object rhs = prepareEvaluator().evaluate(node.getValue());
myNamespace.put(name, prepareEvaluator().concatenate(currentValue, rhs));
}
if (myDeclarationsToTrack.contains(name)) {
List<PyExpression> declarations = myDeclarations.get(name);
if (declarations != null) {
PyPsiUtils.sequenceToList(declarations, node.getValue());
}
}
}
}
@Override
public void visitPyExpressionStatement(PyExpressionStatement node) {
node.getExpression().accept(this);
}
@Override
public void visitPyCallExpression(PyCallExpression node) {
PyExpression callee = node.getCallee();
if (callee instanceof PyReferenceExpression) {
PyReferenceExpression calleeRef = (PyReferenceExpression)callee;
PyExpression qualifier = calleeRef.getQualifier();
if (qualifier instanceof PyReferenceExpression) {
PyReferenceExpression qualifierRef = (PyReferenceExpression)qualifier;
if (!qualifierRef.isQualified()) {
if (PyNames.EXTEND.equals(calleeRef.getReferencedName()) && node.getArguments().length == 1) {
processExtendCall(node, qualifierRef.getReferencedName());
}
else if (PyNames.UPDATE.equals(calleeRef.getReferencedName()) && node.getArguments().length == 1) {
processUpdateCall(node, qualifierRef.getReferencedName());
}
}
}
}
}
@Override
public void visitPyFromImportStatement(PyFromImportStatement node) {
if (node.isFromFuture()) return;
PsiElement source = PyUtil.turnDirIntoInit(node.resolveImportSource());
if (source instanceof PyFile) {
PyBlockEvaluator importEvaluator = new PyBlockEvaluator(myVisitedFiles);
importEvaluator.myDeclarationsToTrack.addAll(myDeclarationsToTrack);
importEvaluator.evaluate((PyFile)source);
if (node.isStarImport()) {
// TODO honor __all__ here
myNamespace.putAll(importEvaluator.myNamespace);
myDeclarations.putAll(importEvaluator.myDeclarations);
}
else {
for (final PyImportElement element : node.getImportElements()) {
final String nameOfVarInOurModule = element.getVisibleName();
final QualifiedName nameOfVarInExternalModule = element.getImportedQName();
if ((nameOfVarInOurModule == null) || (nameOfVarInExternalModule == null)) {
continue;
}
final Object value = importEvaluator.myNamespace.get(nameOfVarInExternalModule.toString());
myNamespace.put(nameOfVarInOurModule, value);
final List<PyExpression> declarations = importEvaluator.getDeclarations(nameOfVarInOurModule);
if (myDeclarations.containsKey(nameOfVarInOurModule)) {
myDeclarations.get(nameOfVarInOurModule).addAll(declarations);
}
else {
myDeclarations.put(nameOfVarInOurModule, declarations);
}
}
}
}
}
@Override
public void visitPyIfStatement(PyIfStatement node) {
PyStatementList list = node.getIfPart().getStatementList();
if (list != null) {
list.acceptChildren(this);
}
}
@Override
public void visitPyReturnStatement(PyReturnStatement node) {
myReturnValue = prepareEvaluator().evaluate(node.getExpression());
}
});
}
private void processExtendCall(PyCallExpression node, String nameBeingExtended) {
PyExpression arg = node.getArguments()[0];
Object value = myNamespace.get(nameBeingExtended);
if (value instanceof List) {
Object argValue = prepareEvaluator().evaluate(arg);
myNamespace.put(nameBeingExtended, prepareEvaluator().concatenate(value, argValue));
}
if (myDeclarationsToTrack.contains(nameBeingExtended)) {
List<PyExpression> declarations = myDeclarations.get(nameBeingExtended);
if (declarations != null) {
PyPsiUtils.sequenceToList(declarations, arg);
}
}
}
private void processUpdateCall(PyCallExpression node, String name) {
Object value = myNamespace.get(name);
if (value instanceof Map) {
Object argValue = prepareEvaluator().evaluate(node.getArguments()[0]);
if (argValue instanceof Map) {
((Map)value).putAll((Map)argValue);
}
}
}
private PyEvaluator prepareEvaluator() {
PyEvaluator evaluator = createEvaluator();
evaluator.setNamespace(myNamespace);
evaluator.setEvaluateCollectionItems(myEvaluateCollectionItems);
return evaluator;
}
protected PyEvaluator createEvaluator() {
return new PyPathEvaluator(myCurrentFilePath);
}
public Object getValue(String name) {
return myNamespace.get(name);
}
@Nullable
public String getValueAsString(String name) {
Object value = myNamespace.get(name);
return value instanceof String ? (String) value : null;
}
@Nullable
public List getValueAsList(String name) {
Object value = myNamespace.get(name);
return value instanceof List ? (List) value : null;
}
@NotNull
public List<String> getValueAsStringList(String name) {
Object value = myNamespace.get(name);
if (value instanceof List) {
List valueList = (List) value;
for (Object o : valueList) {
if (o != null && !(o instanceof String)) {
return Collections.emptyList();
}
}
return (List<String>) value;
}
if (value instanceof String) {
return Collections.singletonList((String) value);
}
return Collections.emptyList();
}
public Set<PyFile> getVisitedFiles() {
return myVisitedFiles;
}
@NotNull
public List<PyExpression> getDeclarations(String name) {
List<PyExpression> expressions = myDeclarations.get(name);
return expressions != null ? expressions : Collections.<PyExpression>emptyList();
}
public Object getReturnValue() {
return myReturnValue;
}
public void setEvaluateCollectionItems(boolean evaluateCollectionItems) {
myEvaluateCollectionItems = evaluateCollectionItems;
}
}
@@ -0,0 +1,314 @@
/*
* Copyright 2000-2013 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.blockEvaluator;
import com.google.common.collect.Sets;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.QualifiedName;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyEvaluator;
import com.jetbrains.python.psi.impl.PyPathEvaluator;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author yole
*/
public class PyBlockEvaluator {
@NotNull
private final PyEvaluationResult myEvaluationResult = new PyEvaluationResult();
@NotNull
private final PyEvaluationContext myContext;
private final Set<PyFile> myVisitedFiles;
private final Set<String> myDeclarationsToTrack = new HashSet<String>();
private String myCurrentFilePath;
private Object myReturnValue;
private boolean myEvaluateCollectionItems = true;
/**
* @param evaluationContext context, obtained via {@link #getContext()}. Pass it here to enable cache. See {@link com.jetbrains.python.psi.impl.blockEvaluator.PyEvaluationContext}
* for more info
* @see com.jetbrains.python.psi.impl.blockEvaluator.PyEvaluationContext
*/
public PyBlockEvaluator(@NotNull final PyEvaluationContext evaluationContext) {
this(Sets.<PyFile>newHashSet(), evaluationContext);
}
/**
* Create evaluator with out of cache context
*/
public PyBlockEvaluator() {
this(new PyEvaluationContext());
}
private PyBlockEvaluator(@NotNull final Set<PyFile> visitedFiles, @NotNull final PyEvaluationContext evaluationContext) {
myVisitedFiles = visitedFiles;
myContext = evaluationContext;
}
public void trackDeclarations(String attrName) {
myDeclarationsToTrack.add(attrName);
}
public void evaluate(PyElement element) {
VirtualFile vFile = element.getContainingFile().getVirtualFile();
myCurrentFilePath = vFile != null ? vFile.getPath() : null;
if (myVisitedFiles.contains(element)) {
return;
}
myVisitedFiles.add((PyFile)element.getContainingFile());
PyElement statementContainer = element instanceof PyFunction ? ((PyFunction)element).getStatementList() : element;
if (statementContainer == null) {
return;
}
statementContainer.acceptChildren(new MyPyElementVisitor());
}
private void processExtendCall(PyCallExpression node, String nameBeingExtended) {
PyExpression arg = node.getArguments()[0];
Object value = myEvaluationResult.myNamespace.get(nameBeingExtended);
if (value instanceof List) {
Object argValue = prepareEvaluator().evaluate(arg);
myEvaluationResult.myNamespace.put(nameBeingExtended, prepareEvaluator().concatenate(value, argValue));
}
if (myDeclarationsToTrack.contains(nameBeingExtended)) {
List<PyExpression> declarations = myEvaluationResult.myDeclarations.get(nameBeingExtended);
if (declarations != null) {
PyPsiUtils.sequenceToList(declarations, arg);
}
}
}
private void processUpdateCall(PyCallExpression node, String name) {
Object value = myEvaluationResult.myNamespace.get(name);
if (value instanceof Map) {
Object argValue = prepareEvaluator().evaluate(node.getArguments()[0]);
if (argValue instanceof Map) {
((Map)value).putAll((Map)argValue);
}
}
}
private PyEvaluator prepareEvaluator() {
PyEvaluator evaluator = createEvaluator();
evaluator.setNamespace(myEvaluationResult.myNamespace);
evaluator.setEvaluateCollectionItems(myEvaluateCollectionItems);
return evaluator;
}
protected PyEvaluator createEvaluator() {
return new PyPathEvaluator(myCurrentFilePath);
}
public Object getValue(String name) {
return myEvaluationResult.myNamespace.get(name);
}
@Nullable
public String getValueAsString(String name) {
Object value = myEvaluationResult.myNamespace.get(name);
return value instanceof String ? (String)value : null;
}
@Nullable
public List getValueAsList(String name) {
Object value = myEvaluationResult.myNamespace.get(name);
return value instanceof List ? (List)value : null;
}
@NotNull
public List<String> getValueAsStringList(String name) {
Object value = myEvaluationResult.myNamespace.get(name);
if (value instanceof List) {
List valueList = (List)value;
for (Object o : valueList) {
if (o != null && !(o instanceof String)) {
return Collections.emptyList();
}
}
return (List<String>)value;
}
if (value instanceof String) {
return Collections.singletonList((String)value);
}
return Collections.emptyList();
}
public Set<PyFile> getVisitedFiles() {
return myVisitedFiles;
}
public Object getReturnValue() {
return myReturnValue;
}
public void setEvaluateCollectionItems(boolean evaluateCollectionItems) {
myEvaluateCollectionItems = evaluateCollectionItems;
}
@NotNull
public List<PyExpression> getDeclarations(@NotNull final String name) {
return myEvaluationResult.getDeclarations(name);
}
/**
* @return so-called context. You may pass it to any instance of {@link com.jetbrains.python.psi.impl.blockEvaluator.PyBlockEvaluator}
* to make instances share their cache
*/
@NotNull
public PyEvaluationContext getContext() {
return myContext;
}
private class MyPyElementVisitor extends PyElementVisitor {
@Override
public void visitPyAssignmentStatement(PyAssignmentStatement node) {
PyExpression expression = node.getLeftHandSideExpression();
if (expression instanceof PyTargetExpression) {
String name = expression.getName();
PyExpression value = ((PyTargetExpression)expression).findAssignedValue();
myEvaluationResult.myNamespace.put(name, prepareEvaluator().evaluate(value));
if (myDeclarationsToTrack.contains(name)) {
List<PyExpression> declarations = new ArrayList<PyExpression>();
PyPsiUtils.sequenceToList(declarations, value);
myEvaluationResult.myDeclarations.put(name, declarations);
}
}
else if (expression instanceof PySubscriptionExpression) {
PyExpression operand = ((PySubscriptionExpression)expression).getOperand();
PyExpression indexExpression = ((PySubscriptionExpression)expression).getIndexExpression();
if (operand instanceof PyReferenceExpression && ((PyReferenceExpression)operand).getQualifier() == null) {
Object currentValue = myEvaluationResult.myNamespace.get(((PyReferenceExpression)operand).getReferencedName());
if (currentValue instanceof Map) {
Object mapKey = prepareEvaluator().evaluate(indexExpression);
if (mapKey != null) {
Object value = myEvaluateCollectionItems ? prepareEvaluator().evaluate(node.getAssignedValue()) : node.getAssignedValue();
((Map)currentValue).put(mapKey, value);
}
}
}
}
}
@Override
public void visitPyAugAssignmentStatement(PyAugAssignmentStatement node) {
PyExpression target = node.getTarget();
String name = target.getName();
if (target instanceof PyReferenceExpression && !((PyReferenceExpression)target).isQualified() && name != null) {
Object currentValue = myEvaluationResult.myNamespace.get(name);
if (currentValue != null) {
Object rhs = prepareEvaluator().evaluate(node.getValue());
myEvaluationResult.myNamespace.put(name, prepareEvaluator().concatenate(currentValue, rhs));
}
if (myDeclarationsToTrack.contains(name)) {
List<PyExpression> declarations = myEvaluationResult.myDeclarations.get(name);
if (declarations != null) {
PyPsiUtils.sequenceToList(declarations, node.getValue());
}
}
}
}
@Override
public void visitPyExpressionStatement(PyExpressionStatement node) {
node.getExpression().accept(this);
}
@Override
public void visitPyCallExpression(PyCallExpression node) {
PyExpression callee = node.getCallee();
if (callee instanceof PyReferenceExpression) {
PyReferenceExpression calleeRef = (PyReferenceExpression)callee;
PyExpression qualifier = calleeRef.getQualifier();
if (qualifier instanceof PyReferenceExpression) {
PyReferenceExpression qualifierRef = (PyReferenceExpression)qualifier;
if (!qualifierRef.isQualified()) {
if (PyNames.EXTEND.equals(calleeRef.getReferencedName()) && node.getArguments().length == 1) {
processExtendCall(node, qualifierRef.getReferencedName());
}
else if (PyNames.UPDATE.equals(calleeRef.getReferencedName()) && node.getArguments().length == 1) {
processUpdateCall(node, qualifierRef.getReferencedName());
}
}
}
}
}
@Override
public void visitPyFromImportStatement(final PyFromImportStatement node) {
if (node.isFromFuture()) return;
final PsiElement source = PyUtil.turnDirIntoInit(node.resolveImportSource());
if (source instanceof PyFile) {
final PyFile pyFile = (PyFile)source;
PyEvaluationResult newlyEvaluatedResult = myContext.getCachedResult(pyFile);
if (newlyEvaluatedResult == null) {
final PyBlockEvaluator importEvaluator = new PyBlockEvaluator(myVisitedFiles, myContext);
importEvaluator.myDeclarationsToTrack.addAll(myDeclarationsToTrack);
importEvaluator.evaluate(pyFile);
newlyEvaluatedResult = importEvaluator.myEvaluationResult;
myContext.cache(pyFile, newlyEvaluatedResult);
}
if (node.isStarImport()) {
// TODO honor __all__ here
myEvaluationResult.myNamespace.putAll(newlyEvaluatedResult.myNamespace);
myEvaluationResult.myDeclarations.putAll(newlyEvaluatedResult.myDeclarations);
}
else {
for (final PyImportElement element : node.getImportElements()) {
final String nameOfVarInOurModule = element.getVisibleName();
final QualifiedName nameOfVarInExternalModule = element.getImportedQName();
if ((nameOfVarInOurModule == null) || (nameOfVarInExternalModule == null)) {
continue;
}
final Object value = newlyEvaluatedResult.myNamespace.get(nameOfVarInExternalModule.toString());
myEvaluationResult.myNamespace.put(nameOfVarInOurModule, value);
final List<PyExpression> declarations = newlyEvaluatedResult.getDeclarations(nameOfVarInOurModule);
if (myEvaluationResult.myDeclarations.containsKey(nameOfVarInOurModule)) {
myEvaluationResult.myDeclarations.get(nameOfVarInOurModule).addAll(declarations);
}
else {
myEvaluationResult.myDeclarations.put(nameOfVarInOurModule, declarations);
}
}
}
}
}
@Override
public void visitPyIfStatement(PyIfStatement node) {
PyStatementList list = node.getIfPart().getStatementList();
if (list != null) {
list.acceptChildren(this);
}
}
@Override
public void visitPyReturnStatement(PyReturnStatement node) {
myReturnValue = prepareEvaluator().evaluate(node.getExpression());
}
}
}
@@ -0,0 +1,42 @@
package com.jetbrains.python.psi.impl.blockEvaluator;
import com.jetbrains.python.psi.PyFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
/**
* Cache for {@link com.jetbrains.python.psi.impl.blockEvaluator.PyBlockEvaluator}.
* You may obtain one via {@link PyBlockEvaluator#getContext()} and pass to ctor:
* {@link com.jetbrains.python.psi.impl.blockEvaluator.PyBlockEvaluator#PyBlockEvaluator(PyEvaluationContext)} to enable cache
*
* @author Ilya.Kazakevich
*/
public class PyEvaluationContext {
@NotNull
private final Map<PyFile, PyEvaluationResult> myResultMap = new HashMap<PyFile, PyEvaluationResult>();
PyEvaluationContext() {
}
/**
* Get evaluation result by file
* @param file file
* @return eval result
*/
@Nullable
PyEvaluationResult getCachedResult(@NotNull final PyFile file) {
return myResultMap.get(file);
}
/**
* Store evaluation result by file
* @param file file
* @param result evaluation result
*/
void cache(@NotNull final PyFile file, @NotNull final PyEvaluationResult result) {
myResultMap.put(file, result);
}
}
@@ -0,0 +1,26 @@
package com.jetbrains.python.psi.impl.blockEvaluator;
import com.jetbrains.python.psi.PyExpression;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Ilya.Kazakevich
*/
@SuppressWarnings("PackageVisibleField") // Package-only class
class PyEvaluationResult {
@NotNull
final Map<String, Object> myNamespace = new HashMap<String, Object>();
@NotNull
final Map<String, List<PyExpression>> myDeclarations = new HashMap<String, List<PyExpression>>();
@NotNull
List<PyExpression> getDeclarations(@NotNull final String name) {
final List<PyExpression> expressions = myDeclarations.get(name);
return (expressions != null) ? expressions : Collections.<PyExpression>emptyList();
}
}
@@ -0,0 +1,7 @@
/**
* Engine to evaluate block of python text.
* Use {@link com.jetbrains.python.psi.impl.blockEvaluator.PyBlockEvaluator} as entry point.
*
* @author Ilya.Kazakevich
*/
package com.jetbrains.python.psi.impl.blockEvaluator;
@@ -21,7 +21,7 @@ import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.PyStringLiteralExpression;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.impl.PyBlockEvaluator;
import com.jetbrains.python.psi.impl.blockEvaluator.PyBlockEvaluator;
import org.junit.Assert;
import java.util.ArrayList;