diff --git a/python/src/META-INF/python-core.xml b/python/src/META-INF/python-core.xml
index 7adbc46c189c..261300a81cd7 100644
--- a/python/src/META-INF/python-core.xml
+++ b/python/src/META-INF/python-core.xml
@@ -137,6 +137,7 @@
+
@@ -476,6 +477,9 @@
+
+
+
myAllElements;
- protected PyFunctionFindUsagesHandler(@NotNull PsiElement psiElement) {
+ public PyFunctionFindUsagesHandler(@NotNull PsiElement psiElement) {
super(psiElement);
myAllElements = null;
}
- protected PyFunctionFindUsagesHandler(@NotNull PsiElement psiElement, List allElements) {
+ public PyFunctionFindUsagesHandler(@NotNull PsiElement psiElement, List allElements) {
super(psiElement);
myAllElements = allElements;
}
diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyCallDataManager.java b/python/src/com/jetbrains/python/hierarchy/call/PyCallDataManager.java
new file mode 100644
index 000000000000..08fd1f2bde89
--- /dev/null
+++ b/python/src/com/jetbrains/python/hierarchy/call/PyCallDataManager.java
@@ -0,0 +1,32 @@
+/*
+ * 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.hierarchy.call;
+
+import com.intellij.psi.PsiElement;
+import com.jetbrains.python.psi.PyElement;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+
+
+/**
+ * @author novokrest
+ */
+public interface PyCallDataManager {
+ Collection getCallees(@NotNull PyElement pyElement);
+
+ Collection getCallers(@NotNull PyElement pyElement);
+}
diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyBrowser.java b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyBrowser.java
new file mode 100644
index 000000000000..69d2f8c31d6d
--- /dev/null
+++ b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyBrowser.java
@@ -0,0 +1,115 @@
+/*
+ * 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.hierarchy.call;
+
+import com.intellij.ide.hierarchy.CallHierarchyBrowserBase;
+import com.intellij.ide.hierarchy.HierarchyNodeDescriptor;
+import com.intellij.ide.hierarchy.HierarchyTreeStructure;
+import com.intellij.ide.util.treeView.NodeDescriptor;
+import com.intellij.openapi.actionSystem.ActionGroup;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.ActionPlaces;
+import com.intellij.openapi.actionSystem.IdeActions;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.psi.PsiElement;
+import com.intellij.ui.PopupHandler;
+import com.jetbrains.python.hierarchy.PyHierarchyUtils;
+import com.jetbrains.python.psi.PyClass;
+import com.jetbrains.python.psi.PyFile;
+import com.jetbrains.python.psi.PyFunction;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.util.Comparator;
+import java.util.Map;
+
+/**
+ * @author novokrest
+ */
+public class PyCallHierarchyBrowser extends CallHierarchyBrowserBase {
+ private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.hierarchy.call.PyCallHierarchyBrowser");
+ private static final String GROUP_PY_CALL_HIERARCHY_POPUP = "PyCallHierarchyPopupMenu";
+
+ public PyCallHierarchyBrowser(PsiElement function) {
+ super(function.getProject(), function);
+ }
+
+ @Nullable
+ @Override
+ protected PsiElement getElementFromDescriptor(@NotNull HierarchyNodeDescriptor descriptor) {
+ if (descriptor instanceof PyCallHierarchyNodeDescriptor) {
+ PyCallHierarchyNodeDescriptor nodeDescriptor = (PyCallHierarchyNodeDescriptor)descriptor;
+ return nodeDescriptor.getEnclosingElement();
+ }
+ return null;
+ }
+
+ @Override
+ protected PsiElement getOpenFileElementFromDescriptor(@NotNull HierarchyNodeDescriptor descriptor) {
+ if (descriptor instanceof PyCallHierarchyNodeDescriptor) {
+ PyCallHierarchyNodeDescriptor nodeDescriptor = (PyCallHierarchyNodeDescriptor)descriptor;
+ return nodeDescriptor.getTargetElement();
+ }
+ return null;
+ }
+
+ @Override
+ protected void createTrees(@NotNull Map type2TreeMap) {
+ ActionGroup group = (ActionGroup)ActionManager.getInstance().getAction(GROUP_PY_CALL_HIERARCHY_POPUP);
+ final JTree tree1 = createTree(false);
+ PopupHandler.installPopupHandler(tree1, group, ActionPlaces.CALL_HIERARCHY_VIEW_POPUP, ActionManager.getInstance());
+ final BaseOnThisFunctionAction baseOnThisFunctionAction = new BaseOnThisFunctionAction();
+ baseOnThisFunctionAction
+ .registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_CALL_HIERARCHY).getShortcutSet(), tree1);
+ type2TreeMap.put(CALLER_TYPE, tree1);
+
+ final JTree tree2 = createTree(false);
+ PopupHandler.installPopupHandler(tree2, group, ActionPlaces.CALL_HIERARCHY_VIEW_POPUP, ActionManager.getInstance());
+ baseOnThisFunctionAction
+ .registerCustomShortcutSet(ActionManager.getInstance().getAction(IdeActions.ACTION_CALL_HIERARCHY).getShortcutSet(), tree2);
+ type2TreeMap.put(CALLEE_TYPE, tree2);
+ }
+
+ @Override
+ protected boolean isApplicableElement(@NotNull PsiElement element) {
+ return element instanceof PyFunction || element instanceof PyClass || element instanceof PyFile;
+ }
+
+ @Nullable
+ @Override
+ protected HierarchyTreeStructure createHierarchyTreeStructure(@NotNull String typeName, @NotNull PsiElement psiElement) {
+ if (CALLER_TYPE.equals(typeName)) {
+ return new PyCallerFunctionTreeStructure(myProject, psiElement, getCurrentScopeType());
+ }
+ else if (CALLEE_TYPE.equals(typeName)) {
+ return new PyCalleeFunctionTreeStructure(myProject, psiElement, getCurrentScopeType());
+ }
+ else {
+ LOG.error("unexpected type: " + typeName);
+ return null;
+ }
+ }
+
+ @Nullable
+ @Override
+ protected Comparator getComparator() {
+ return PyHierarchyUtils.getComparator(myProject);
+ }
+
+ public static final class BaseOnThisFunctionAction extends CallHierarchyBrowserBase.BaseOnThisMethodAction {
+ }
+}
diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyNodeDescriptor.java b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyNodeDescriptor.java
new file mode 100644
index 000000000000..2aff7a06e4e7
--- /dev/null
+++ b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyNodeDescriptor.java
@@ -0,0 +1,240 @@
+/*
+ * 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.hierarchy.call;
+
+import com.intellij.codeInsight.highlighting.HighlightManager;
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.IdeBundle;
+import com.intellij.ide.hierarchy.HierarchyNodeDescriptor;
+import com.intellij.ide.util.treeView.NodeDescriptor;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.markup.RangeHighlighter;
+import com.intellij.openapi.editor.markup.TextAttributes;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ui.util.CompositeAppearance;
+import com.intellij.openapi.util.Comparing;
+import com.intellij.openapi.util.Iconable;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.pom.Navigatable;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiReference;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.psi.util.PsiUtilBase;
+import com.intellij.ui.LayeredIcon;
+import com.jetbrains.python.psi.*;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author novokrest
+ */
+public class PyCallHierarchyNodeDescriptor extends HierarchyNodeDescriptor implements Navigatable {
+ private int myUsageCount = 1;
+ private final List myReferences = new ArrayList();
+ private final boolean myNavigateToReference;
+
+ protected PyCallHierarchyNodeDescriptor(@NotNull Project project,
+ @Nullable NodeDescriptor parentDescriptor,
+ @NotNull PsiElement element,
+ boolean isBase,
+ boolean navigateToReference) {
+ super(project, parentDescriptor, element, isBase);
+ myNavigateToReference = navigateToReference;
+ }
+
+ public final void incrementCount() {
+ myUsageCount++;
+ }
+
+ public final PyFunction getPyFunction() {
+ return (PyFunction)myElement;
+ }
+
+ public final PyElement getEnclosingElement() {
+ return PsiTreeUtil.getNonStrictParentOfType(myElement, PyFunction.class, PyClass.class, PyFile.class);
+ }
+
+ public final PsiElement getTargetElement() {
+ return myElement;
+ }
+
+ @Override
+ public boolean isValid() {
+ return myElement != null && myElement.isValid();
+ }
+
+ @Override
+ public boolean update() {
+ final CompositeAppearance oldText = myHighlightedText;
+ final Icon oldIcon = getIcon();
+
+ int flags = Iconable.ICON_FLAG_VISIBILITY;
+ if (isMarkReadOnly()) {
+ flags |= Iconable.ICON_FLAG_READ_STATUS;
+ }
+
+ final PsiElement element = getEnclosingElement();
+ if (element == null) {
+ final String invalidPrefix = IdeBundle.message("node.hierarchy.invalid");
+ if (!myHighlightedText.getText().startsWith(invalidPrefix)) {
+ myHighlightedText.getBeginning().addText(invalidPrefix, HierarchyNodeDescriptor.getInvalidPrefixAttributes());
+ }
+ return true;
+ }
+
+ boolean changes = super.update();
+
+ Icon newIcon = element.getIcon(flags);
+ if (changes && myIsBase) {
+ final LayeredIcon icon = new LayeredIcon(2);
+ icon.setIcon(newIcon, 0);
+ icon.setIcon(AllIcons.Hierarchy.Base, 1, -AllIcons.Hierarchy.Base.getIconWidth() / 2, 0);
+ newIcon = icon;
+ }
+ setIcon(newIcon);
+
+ myHighlightedText = new CompositeAppearance();
+ final TextAttributes mainTextAttributes = myColor != null ? null : new TextAttributes(myColor, null, null, null, Font.PLAIN);
+
+ PyElementVisitor visitor = new PyElementVisitor() {
+ @Override
+ public void visitPyClass(PyClass pyClass) {
+ myHighlightedText.getEnding().addText(pyClass.getName(), mainTextAttributes);
+ myHighlightedText.getEnding().addText("(" + pyClass.getContainingFile().getName() + ")",
+ HierarchyNodeDescriptor.getPackageNameAttributes());
+ }
+
+ @Override
+ public void visitPyFunction(PyFunction function) {
+ final StringBuilder buffer = new StringBuilder();
+ final PyClass pyClass = function.getContainingClass();
+ if (pyClass != null) {
+ buffer.append(pyClass.getName());
+ buffer.append('.');
+ }
+ buffer.append(function.getName());
+
+ myHighlightedText.getEnding().addText(buffer.toString(), mainTextAttributes);
+ myHighlightedText.getEnding().addText("(" + function.getContainingFile().getName() + ")",
+ HierarchyNodeDescriptor.getPackageNameAttributes());
+ }
+
+ @Override
+ public void visitPyLambdaExpression(PyLambdaExpression node) {
+ }
+
+ @Override
+ public void visitPyFile(PyFile pyFile) {
+ myHighlightedText.getEnding().addText(pyFile.getName(), mainTextAttributes);
+ }
+ };
+
+ element.accept(visitor);
+
+ if (myUsageCount > 1) {
+ myHighlightedText.getEnding()
+ .addText(IdeBundle.message("node.call.hierarchy.N.usages", myUsageCount), HierarchyNodeDescriptor.getUsageCountPrefixAttributes());
+ }
+
+ myName = myHighlightedText.getText();
+ if (!Comparing.equal(myHighlightedText, oldText)
+ || !Comparing.equal(getIcon(), oldIcon)) {
+ return true;
+ }
+
+ return changes;
+ }
+
+ public void addReference(final PsiReference reference) {
+ myReferences.add(reference);
+ }
+
+ public boolean hasReference(final PsiReference reference) {
+ return myReferences.contains(reference);
+ }
+
+
+ @Override
+ public void navigate(boolean requestFocus) {
+ if (!myNavigateToReference) {
+ if (myElement instanceof Navigatable && ((Navigatable)myElement).canNavigate()) {
+ ((Navigatable)myElement).navigate(requestFocus);
+ }
+ return;
+ }
+ final PsiReference firstReference = myReferences.get(0);
+ final PsiElement element = firstReference.getElement();
+ if (element == null) return;
+ final PsiElement callElement = element.getParent();
+ if (callElement instanceof Navigatable && ((Navigatable)callElement).canNavigate()) {
+ ((Navigatable)callElement).navigate(requestFocus);
+ }
+ else {
+ final PsiFile file = callElement.getContainingFile();
+ if (file == null || file.getVirtualFile() == null) return;
+ FileEditorManager.getInstance(myProject).openFile(file.getVirtualFile(), requestFocus);
+ }
+
+ Editor editor = PsiUtilBase.findEditor(callElement);
+ if (editor != null) {
+ HighlightManager highlightManager = HighlightManager.getInstance(myProject);
+ EditorColorsManager colorsManager = EditorColorsManager.getInstance();
+ TextAttributes attributes = colorsManager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES);
+ List highlighters = new ArrayList();
+ for (PsiReference reference : myReferences) {
+ final PsiElement eachElement = reference.getElement();
+ if (eachElement != null) {
+ PsiElement eachCallElement = eachElement.getParent();
+ if (eachCallElement != null) {
+ final TextRange textRange = eachCallElement.getTextRange();
+ highlightManager
+ .addRangeHighlight(editor, textRange.getStartOffset(), textRange.getEndOffset(), attributes, false, highlighters);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean canNavigate() {
+ if (!myNavigateToReference) {
+ return myElement instanceof Navigatable && ((Navigatable)myElement).canNavigate();
+ }
+ if (myReferences.isEmpty()) return false;
+ final PsiReference reference = myReferences.get(0);
+ final PsiElement callElement = reference.getElement().getParent();
+ if (callElement == null || !callElement.isValid()) return false;
+ if (!(callElement instanceof Navigatable) || !((Navigatable)callElement).canNavigate()) {
+ final PsiFile file = callElement.getContainingFile();
+ if (file == null) return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean canNavigateToSource() {
+ return canNavigate();
+ }
+}
diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyProvider.java b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyProvider.java
new file mode 100644
index 000000000000..67c0d6ddfa0b
--- /dev/null
+++ b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyProvider.java
@@ -0,0 +1,76 @@
+/*
+ * 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.hierarchy.call;
+
+import com.intellij.codeInsight.TargetElementUtilBase;
+import com.intellij.ide.hierarchy.CallHierarchyBrowserBase;
+import com.intellij.ide.hierarchy.HierarchyBrowser;
+import com.intellij.ide.hierarchy.HierarchyProvider;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.jetbrains.python.psi.PyClass;
+import com.jetbrains.python.psi.PyFile;
+import com.jetbrains.python.psi.PyFunction;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author novokrest
+ */
+public class PyCallHierarchyProvider implements HierarchyProvider {
+ @Nullable
+ @Override
+ public PsiElement getTarget(@NotNull DataContext dataContext) {
+ Project project = CommonDataKeys.PROJECT.getData(dataContext);
+ if (project == null) return null;
+
+ PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext);
+ if (element == null) {
+ Editor editor = CommonDataKeys.EDITOR.getData(dataContext);
+ if (editor != null) {
+ PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
+ if (file == null) return null;
+
+ element = TargetElementUtilBase.findTargetElement(editor, TargetElementUtilBase.ELEMENT_NAME_ACCEPTED |
+ TargetElementUtilBase.REFERENCED_ELEMENT_ACCEPTED |
+ TargetElementUtilBase.LOOKUP_ITEM_ACCEPTED);
+ if (element instanceof PyFunction || element instanceof PyClass || element instanceof PyFile) {
+ return element;
+ }
+
+ element = file.findElementAt(editor.getCaretModel().getOffset());
+ }
+ }
+ return PsiTreeUtil.getNonStrictParentOfType(element, PyFunction.class, PyClass.class, PyFile.class);
+ }
+
+ @NotNull
+ @Override
+ public HierarchyBrowser createHierarchyBrowser(PsiElement target) {
+ return new PyCallHierarchyBrowser(target);
+ }
+
+ @Override
+ public void browserActivated(@NotNull HierarchyBrowser hierarchyBrowser) {
+ ((PyCallHierarchyBrowser)hierarchyBrowser).changeView(CallHierarchyBrowserBase.CALLER_TYPE);
+ }
+}
diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyCalleeFunctionTreeStructure.java b/python/src/com/jetbrains/python/hierarchy/call/PyCalleeFunctionTreeStructure.java
new file mode 100644
index 000000000000..d3106dd7dbc3
--- /dev/null
+++ b/python/src/com/jetbrains/python/hierarchy/call/PyCalleeFunctionTreeStructure.java
@@ -0,0 +1,88 @@
+/*
+ * 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.hierarchy.call;
+
+import com.google.common.collect.Lists;
+import com.intellij.ide.hierarchy.HierarchyNodeDescriptor;
+import com.intellij.ide.hierarchy.HierarchyTreeStructure;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiElement;
+import com.intellij.util.ArrayUtil;
+import com.jetbrains.python.psi.PyClass;
+import com.jetbrains.python.psi.PyElement;
+import com.jetbrains.python.psi.PyFile;
+import com.jetbrains.python.psi.PyFunction;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author novokrest
+ */
+public class PyCalleeFunctionTreeStructure extends HierarchyTreeStructure {
+ private final String myScopeType;
+
+ public PyCalleeFunctionTreeStructure(Project project, PsiElement element, String currentScopeType) {
+ super(project, new PyCallHierarchyNodeDescriptor(project, null, element, true, false));
+ myScopeType = currentScopeType;
+ }
+
+ @NotNull
+ @Override
+ protected Object[] buildChildren(@NotNull HierarchyNodeDescriptor descriptor) {
+ final PyElement element = ((PyCallHierarchyNodeDescriptor)descriptor).getEnclosingElement();
+ final boolean isCallable = element instanceof PyFunction || element instanceof PyClass || element instanceof PyFile;
+ HierarchyNodeDescriptor nodeDescriptor = getBaseDescriptor();
+ if (!isCallable || nodeDescriptor == null) {
+ return ArrayUtil.EMPTY_OBJECT_ARRAY;
+ }
+
+ final List callees = Lists.newArrayList();
+
+ PyCallDataManager[] functionManagers = {
+ // TODO: Add dynamic call data manager
+ PyStaticCallDataManager.getInstance(myProject),
+ };
+
+ for (PyCallDataManager functionManager : functionManagers) {
+ callees.addAll(functionManager.getCallees(element));
+ }
+
+ final Map calleeToDescriptorMap = new HashMap();
+ final List descriptors = Lists.newArrayList();
+ PsiElement baseClass = element instanceof PyFunction ? ((PyFunction)element).getContainingClass() : null;
+
+ for (PsiElement callee : callees) {
+ if (baseClass != null && !isInScope(baseClass, callee, myScopeType)) continue;
+
+ PyCallHierarchyNodeDescriptor calleeDescriptor = calleeToDescriptorMap.get(callee);
+ if (calleeDescriptor == null) {
+ calleeDescriptor = new PyCallHierarchyNodeDescriptor(myProject, null, callee, false, false);
+ calleeToDescriptorMap.put(callee, calleeDescriptor);
+ descriptors.add(calleeDescriptor);
+ }
+ }
+
+ return ArrayUtil.toObjectArray(descriptors);
+ }
+
+ @Override
+ public boolean isAlwaysShowPlus() {
+ return true;
+ }
+}
diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyCallerFunctionTreeStructure.java b/python/src/com/jetbrains/python/hierarchy/call/PyCallerFunctionTreeStructure.java
new file mode 100644
index 000000000000..844f728381e8
--- /dev/null
+++ b/python/src/com/jetbrains/python/hierarchy/call/PyCallerFunctionTreeStructure.java
@@ -0,0 +1,87 @@
+/*
+ * 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.hierarchy.call;
+
+import com.google.common.collect.Lists;
+import com.intellij.ide.hierarchy.HierarchyNodeDescriptor;
+import com.intellij.ide.hierarchy.HierarchyTreeStructure;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiElement;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.containers.HashMap;
+import com.jetbrains.python.psi.PyClass;
+import com.jetbrains.python.psi.PyElement;
+import com.jetbrains.python.psi.PyFile;
+import com.jetbrains.python.psi.PyFunction;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * @author novokrest
+ */
+public class PyCallerFunctionTreeStructure extends HierarchyTreeStructure {
+ private final String myScopeType;
+
+ public PyCallerFunctionTreeStructure(Project project, PsiElement element, String currentScopeType) {
+ super(project, new PyCallHierarchyNodeDescriptor(project, null, element, true, false));
+ myScopeType = currentScopeType;
+ }
+
+ @NotNull
+ @Override
+ protected Object[] buildChildren(@NotNull HierarchyNodeDescriptor descriptor) {
+ final PyElement element = ((PyCallHierarchyNodeDescriptor)descriptor).getEnclosingElement();
+ final boolean isCallable = element instanceof PyFunction || element instanceof PyClass || element instanceof PyFile;
+ HierarchyNodeDescriptor nodeDescriptor = getBaseDescriptor();
+ if (!isCallable || nodeDescriptor == null) {
+ return ArrayUtil.EMPTY_OBJECT_ARRAY;
+ }
+
+ final List callers = Lists.newArrayList();
+
+ PyCallDataManager[] functionManagers = {
+ // TODO: Add dynamic call data manager
+ PyStaticCallDataManager.getInstance(myProject),
+ };
+
+ for (PyCallDataManager functionManager : functionManagers) {
+ callers.addAll(functionManager.getCallers(element));
+ }
+
+ final HashMap callerToDescriptorMap = new HashMap();
+ final List descriptors = Lists.newArrayList();
+ PsiElement baseClass = element instanceof PyFunction ? ((PyFunction)element).getContainingClass() : null;
+
+ for (PsiElement caller : callers) {
+ if (baseClass != null && !isInScope(baseClass, caller, myScopeType)) continue;
+
+ PyCallHierarchyNodeDescriptor callerDescriptor = callerToDescriptorMap.get(caller);
+ if (callerDescriptor == null) {
+ callerDescriptor = new PyCallHierarchyNodeDescriptor(myProject, null, caller, false, false);
+ callerToDescriptorMap.put(caller, callerDescriptor);
+ descriptors.add(callerDescriptor);
+ }
+ }
+
+ return ArrayUtil.toObjectArray(descriptors);
+ }
+
+ @Override
+ public boolean isAlwaysShowPlus() {
+ return true;
+ }
+}
diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyStaticCallDataManager.java b/python/src/com/jetbrains/python/hierarchy/call/PyStaticCallDataManager.java
new file mode 100644
index 000000000000..ed7c2f12721a
--- /dev/null
+++ b/python/src/com/jetbrains/python/hierarchy/call/PyStaticCallDataManager.java
@@ -0,0 +1,28 @@
+/*
+ * 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.hierarchy.call;
+
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+
+/**
+ * @author novokrest
+ */
+public abstract class PyStaticCallDataManager implements PyCallDataManager {
+ public static PyCallDataManager getInstance(Project project) {
+ return ServiceManager.getService(project, PyStaticCallDataManager.class);
+ }
+}
diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyStaticCallDataManagerImpl.java b/python/src/com/jetbrains/python/hierarchy/call/PyStaticCallDataManagerImpl.java
new file mode 100644
index 000000000000..f918638dc936
--- /dev/null
+++ b/python/src/com/jetbrains/python/hierarchy/call/PyStaticCallDataManagerImpl.java
@@ -0,0 +1,165 @@
+/*
+ * 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.hierarchy.call;
+
+import com.google.common.collect.Lists;
+import com.intellij.find.findUsages.FindUsagesHandler;
+import com.intellij.find.findUsages.FindUsagesOptions;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.usageView.UsageInfo;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.CommonProcessors;
+import com.jetbrains.python.PyNames;
+import com.jetbrains.python.findUsages.PyClassFindUsagesHandler;
+import com.jetbrains.python.findUsages.PyFunctionFindUsagesHandler;
+import com.jetbrains.python.psi.*;
+import com.jetbrains.python.psi.impl.PyBuiltinCache;
+import com.jetbrains.python.psi.resolve.PyResolveContext;
+import com.jetbrains.python.psi.search.PySuperMethodsSearch;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @author novokrest
+ */
+public class PyStaticCallDataManagerImpl extends PyStaticCallDataManager {
+ @Override
+ public Collection getCallees(@NotNull PyElement element) {
+ final List callees = Lists.newArrayList();
+
+ final PyRecursiveElementVisitor visitor = new PyRecursiveElementVisitor() {
+ @Override
+ public void visitPyParameterList(PyParameterList node) {
+ }
+
+ @Override
+ public void visitPyLambdaExpression(PyLambdaExpression node) {
+ }
+
+ @Override
+ public void visitPyFunction(PyFunction innerFunction) {
+ for (PyParameter parameter : innerFunction.getParameterList().getParameters()) {
+ PsiElement defaultValue = parameter.getDefaultValue();
+ if (defaultValue != null) {
+ defaultValue.accept(this);
+ }
+ }
+ }
+
+ @Override
+ public void visitPyCallExpression(PyCallExpression callExpression) {
+ super.visitPyCallExpression(callExpression);
+ PsiElement calleeFunction = callExpression.resolveCalleeFunction(PyResolveContext.defaultContext());
+ if (calleeFunction instanceof PyFunction) {
+ callees.add(calleeFunction);
+ }
+ }
+ };
+
+ visitor.visitElement(element);
+
+ return callees;
+ }
+
+ @Override
+ public Collection getCallers(@NotNull PyElement pyElement) {
+ final List callers = Lists.newArrayList();
+ final Collection usages = findUsages(pyElement);
+
+ for (UsageInfo usage : usages) {
+ PsiElement element = usage.getElement();
+ if (element == null) {
+ continue;
+ }
+
+ element = element.getParent();
+ while (element instanceof PyParenthesizedExpression) {
+ element = element.getParent();
+ }
+
+ if (element instanceof PyCallExpression) {
+ PsiElement caller = PsiTreeUtil.getParentOfType(element, PyParameterList.class, PyFunction.class);
+ if (caller instanceof PyFunction) {
+ callers.add(caller);
+ }
+ else if (caller instanceof PyParameterList) {
+ PsiElement innerFunction = PsiTreeUtil.getParentOfType(caller, PyFunction.class);
+ PsiElement outerFunction = PsiTreeUtil.getParentOfType(innerFunction, PyFunction.class);
+ if (innerFunction != null && outerFunction != null) {
+ callers.add(outerFunction);
+ }
+ }
+ }
+ }
+
+ return callers;
+ }
+
+ private static Collection findUsages(@NotNull final PsiElement element) {
+ final FindUsagesHandler handler = createFindUsageHandler(element);
+ if (handler == null) {
+ return Lists.newArrayList();
+ }
+ final CommonProcessors.CollectProcessor processor = new CommonProcessors.CollectProcessor();
+ final PsiElement[] psiElements = ArrayUtil.mergeArrays(handler.getPrimaryElements(), handler.getSecondaryElements());
+ final FindUsagesOptions options = handler.getFindUsagesOptions(null);
+ for (PsiElement psiElement : psiElements) {
+ handler.processElementUsages(psiElement, processor, options);
+ }
+ return processor.getResults();
+ }
+
+ /**
+ * @see {@link com.jetbrains.python.findUsages.PyFindUsagesHandlerFactory#createFindUsagesHandler(com.intellij.psi.PsiElement, boolean) createFindUsagesHandler}
+ */
+ @Nullable
+ private static FindUsagesHandler createFindUsageHandler(@NotNull final PsiElement element) {
+ if (element instanceof PyFunction) {
+ final Collection superMethods = PySuperMethodsSearch.search((PyFunction)element, true).findAll();
+ if (superMethods.size() > 0) {
+ final PsiElement next = superMethods.iterator().next();
+ if (next instanceof PyFunction && !isInObject((PyFunction)next)) {
+ List allMethods = Lists.newArrayList();
+ allMethods.add(element);
+ allMethods.addAll(superMethods);
+
+ return new PyFunctionFindUsagesHandler(element, allMethods);
+ }
+ }
+ return new PyFunctionFindUsagesHandler(element);
+ }
+ if (element instanceof PyClass) {
+ return new PyClassFindUsagesHandler((PyClass)element);
+ }
+ return null;
+ }
+
+ /**
+ * @see {@link com.jetbrains.python.findUsages.PyFindUsagesHandlerFactory#isInObject(com.jetbrains.python.psi.PyFunction) isInObject}
+ */
+ private static boolean isInObject(PyFunction fun) {
+ final PyClass containingClass = fun.getContainingClass();
+ if (containingClass == null) {
+ return false;
+ }
+ return (PyNames.FAKE_OLD_BASE.equals(containingClass.getName()) ||
+ (PyNames.OBJECT.equals(containingClass.getName()) && PyBuiltinCache.getInstance(fun).isBuiltin(containingClass)));
+ }
+}
diff --git a/python/testData/hierarchy/call/Static/ArgumentList/ArgumentList_callee_verification.xml b/python/testData/hierarchy/call/Static/ArgumentList/ArgumentList_callee_verification.xml
new file mode 100644
index 000000000000..2f7b31c8602e
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/ArgumentList/ArgumentList_callee_verification.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/ArgumentList/ArgumentList_caller_verification.xml b/python/testData/hierarchy/call/Static/ArgumentList/ArgumentList_caller_verification.xml
new file mode 100644
index 000000000000..2f7b31c8602e
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/ArgumentList/ArgumentList_caller_verification.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/ArgumentList/file_1.py b/python/testData/hierarchy/call/Static/ArgumentList/file_1.py
new file mode 100644
index 000000000000..ea497204d2ca
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/ArgumentList/file_1.py
@@ -0,0 +1,8 @@
+def target_func():
+ pass
+
+def func1(f):
+ f()
+
+def func2(f):
+ return f
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/ArgumentList/main.py b/python/testData/hierarchy/call/Static/ArgumentList/main.py
new file mode 100644
index 000000000000..b479ea6f9d3a
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/ArgumentList/main.py
@@ -0,0 +1,8 @@
+from file_1 import *
+
+target_func()
+func1(target_func)
+func1(target_func())
+func2(target_func)
+func2(target_func())
+
diff --git a/python/testData/hierarchy/call/Static/Constructor/Constructor_callee_verification.xml b/python/testData/hierarchy/call/Static/Constructor/Constructor_callee_verification.xml
new file mode 100644
index 000000000000..4e9a681100a1
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Constructor/Constructor_callee_verification.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Constructor/Constructor_caller_verification.xml b/python/testData/hierarchy/call/Static/Constructor/Constructor_caller_verification.xml
new file mode 100644
index 000000000000..765e6f5f9e47
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Constructor/Constructor_caller_verification.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Constructor/main.py b/python/testData/hierarchy/call/Static/Constructor/main.py
new file mode 100644
index 000000000000..1c2e14776c25
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Constructor/main.py
@@ -0,0 +1,32 @@
+class A():
+ def __init__(self):
+ invoke1(self)
+ invoke2(self)
+
+ def method1(self):
+ pass
+
+ def method2(self):
+ pass
+
+def invoke1(p):
+ p.method1()
+
+
+def invoke2(p):
+ p.method2()
+
+
+def invokeA():
+ a = A()
+ a.method1()
+ a.method2()
+
+ def new_class_func():
+ class C():
+ def bar(self):
+ invokeA(A())
+ return C()
+
+a = A()
+A.__init__(a)
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/DefaultValue/DefaultValue_callee_verification.xml b/python/testData/hierarchy/call/Static/DefaultValue/DefaultValue_callee_verification.xml
new file mode 100644
index 000000000000..98e62b66545f
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/DefaultValue/DefaultValue_callee_verification.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/DefaultValue/DefaultValue_caller_verification.xml b/python/testData/hierarchy/call/Static/DefaultValue/DefaultValue_caller_verification.xml
new file mode 100644
index 000000000000..bb2ed79ce71e
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/DefaultValue/DefaultValue_caller_verification.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/DefaultValue/main.py b/python/testData/hierarchy/call/Static/DefaultValue/main.py
new file mode 100644
index 000000000000..223642493ef4
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/DefaultValue/main.py
@@ -0,0 +1,30 @@
+def target_func():
+ pass
+
+
+def func1():
+ def inner_func1(x=target_func):
+ pass
+
+ return inner_func1(target_func)
+
+
+def func2():
+ def inner_func2(x=target_func()):
+ pass
+
+ return inner_func2(target_func)
+
+
+def func3(x=target_func()):
+ pass
+
+
+def func4():
+ def inner_func4(x=target_func):
+ pass
+
+ return inner_func4(target_func())
+
+
+target_func()
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Inheritance/Inheritance_callee_verification.xml b/python/testData/hierarchy/call/Static/Inheritance/Inheritance_callee_verification.xml
new file mode 100644
index 000000000000..df150105b3a1
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Inheritance/Inheritance_callee_verification.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Inheritance/Inheritance_caller_verification.xml b/python/testData/hierarchy/call/Static/Inheritance/Inheritance_caller_verification.xml
new file mode 100644
index 000000000000..678468dbc8cd
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Inheritance/Inheritance_caller_verification.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Inheritance/main.py b/python/testData/hierarchy/call/Static/Inheritance/main.py
new file mode 100644
index 000000000000..89c37de9bceb
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Inheritance/main.py
@@ -0,0 +1,36 @@
+
+class A(object):
+ def target_func(self):
+ pass
+
+
+class B(A):
+ pass
+
+
+class C(object):
+ def func(self, a):
+ a.target_func()
+
+
+def foo1(b):
+ f = b.target_func
+
+
+def foo2(b):
+ b.target_func()
+
+
+def bar1(*args):
+ pass
+
+
+def bar2(*args):
+ pass
+
+
+b = B()
+foo1(b)
+foo2(b)
+bar1(b.target_func)
+bar2(b.target_func())
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/InnerFunction/InnerFunction_callee_verification.xml b/python/testData/hierarchy/call/Static/InnerFunction/InnerFunction_callee_verification.xml
new file mode 100644
index 000000000000..dac68ae1548e
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/InnerFunction/InnerFunction_callee_verification.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/InnerFunction/InnerFunction_caller_verification.xml b/python/testData/hierarchy/call/Static/InnerFunction/InnerFunction_caller_verification.xml
new file mode 100644
index 000000000000..98e62b66545f
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/InnerFunction/InnerFunction_caller_verification.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/InnerFunction/main.py b/python/testData/hierarchy/call/Static/InnerFunction/main.py
new file mode 100644
index 000000000000..7ffc14aaf745
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/InnerFunction/main.py
@@ -0,0 +1,9 @@
+def bar():
+ pass
+
+def target_func():
+ def foo():
+ return bar()
+ foo()
+
+target_func()
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Lambda/Lambda_callee_verification.xml b/python/testData/hierarchy/call/Static/Lambda/Lambda_callee_verification.xml
new file mode 100644
index 000000000000..8e2ff7ea0ab0
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Lambda/Lambda_callee_verification.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Lambda/Lambda_caller_verification.xml b/python/testData/hierarchy/call/Static/Lambda/Lambda_caller_verification.xml
new file mode 100644
index 000000000000..98e62b66545f
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Lambda/Lambda_caller_verification.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Lambda/file_1.py b/python/testData/hierarchy/call/Static/Lambda/file_1.py
new file mode 100644
index 000000000000..bf1185212963
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Lambda/file_1.py
@@ -0,0 +1,18 @@
+def func1(): pass
+def func2(): pass
+def func3(): pass
+def func4(): pass
+def func5(): pass
+def func6(): pass
+def func7(): pass
+def func8(): pass
+def func9(): pass
+def func10(): pass
+def func11(): pass
+def func12(): pass
+def func13(): pass
+def func14(): pass
+def func15(): pass
+def func16(): pass
+def func17(): pass
+def func18(): pass
diff --git a/python/testData/hierarchy/call/Static/Lambda/main.py b/python/testData/hierarchy/call/Static/Lambda/main.py
new file mode 100644
index 000000000000..7dab14e27566
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Lambda/main.py
@@ -0,0 +1,18 @@
+from file_1 import *
+
+
+def target_func(x=func1, y=func2(), z=lambda: func3, w=lambda: func4()):
+ p1 = lambda: func5()
+ p2 = lambda: func6
+ p1(), p2()
+ def inner(ix=func7, iy=func8(), iz=lambda: func9, iw=lambda: func10()):
+ func11()
+ ip = lambda: func12()
+ ip()
+ func13()
+ inner(func14, func15(), lambda: func16, lambda: func17())
+
+ return func18
+
+
+target_func()
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/NestedCall/NestedCall_callee_verification.xml b/python/testData/hierarchy/call/Static/NestedCall/NestedCall_callee_verification.xml
new file mode 100644
index 000000000000..2f7335511a1a
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/NestedCall/NestedCall_callee_verification.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/NestedCall/NestedCall_caller_verification.xml b/python/testData/hierarchy/call/Static/NestedCall/NestedCall_caller_verification.xml
new file mode 100644
index 000000000000..98e62b66545f
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/NestedCall/NestedCall_caller_verification.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/NestedCall/file_1.py b/python/testData/hierarchy/call/Static/NestedCall/file_1.py
new file mode 100644
index 000000000000..3e89b6b290d0
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/NestedCall/file_1.py
@@ -0,0 +1,10 @@
+def func1(*args): pass
+def func2(*args): pass
+def func3(*args): pass
+def func4(*args): pass
+def func5(*args): pass
+def func6(*args): pass
+def func7(*args): pass
+def func8(*args): pass
+def func9(*args): pass
+def func10(*args): pass
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/NestedCall/main.py b/python/testData/hierarchy/call/Static/NestedCall/main.py
new file mode 100644
index 000000000000..aca993a9d6a1
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/NestedCall/main.py
@@ -0,0 +1,10 @@
+from file_1 import *
+
+def target_func():
+ def inner(*args):
+ return func1()
+
+ return inner(func1(func2(func3(func4, func5()), func6(), (((func7)))()), func8), func9, func10())
+
+
+target_func()
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/OverriddenMethod/OverriddenMethod_callee_verification.xml b/python/testData/hierarchy/call/Static/OverriddenMethod/OverriddenMethod_callee_verification.xml
new file mode 100644
index 000000000000..e68efab80f7b
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/OverriddenMethod/OverriddenMethod_callee_verification.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/OverriddenMethod/OverriddenMethod_caller_verification.xml b/python/testData/hierarchy/call/Static/OverriddenMethod/OverriddenMethod_caller_verification.xml
new file mode 100644
index 000000000000..20fdb7938d8c
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/OverriddenMethod/OverriddenMethod_caller_verification.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/OverriddenMethod/file_1.py b/python/testData/hierarchy/call/Static/OverriddenMethod/file_1.py
new file mode 100644
index 000000000000..e1e4fb90b949
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/OverriddenMethod/file_1.py
@@ -0,0 +1,8 @@
+
+class A(object):
+ def target_func(self, p):
+ pass
+
+ def another_func(self):
+ pass
+
diff --git a/python/testData/hierarchy/call/Static/OverriddenMethod/main.py b/python/testData/hierarchy/call/Static/OverriddenMethod/main.py
new file mode 100644
index 000000000000..288d638e0fea
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/OverriddenMethod/main.py
@@ -0,0 +1,29 @@
+from file_1 import A
+
+
+class B(A):
+ def target_func(self, p):
+ p.another_func()
+
+
+class C(object):
+ def func1(self, a):
+ a.target_func(A())
+
+ def func2(self):
+ a = A()
+ b = B()
+ a.target_func(b)
+
+
+def bar1(a):
+ a.target_func(a)
+
+
+def bar2(a, b):
+ atf, btf = a.target_func, b.target_func
+
+
+bar1(A())
+bar2(A(), B())
+B().target_func(A())
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Parentheses/Parentheses_callee_verification.xml b/python/testData/hierarchy/call/Static/Parentheses/Parentheses_callee_verification.xml
new file mode 100644
index 000000000000..5c6e293e2220
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Parentheses/Parentheses_callee_verification.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Parentheses/Parentheses_caller_verification.xml b/python/testData/hierarchy/call/Static/Parentheses/Parentheses_caller_verification.xml
new file mode 100644
index 000000000000..fca38e3b7359
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Parentheses/Parentheses_caller_verification.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Parentheses/file_1.py b/python/testData/hierarchy/call/Static/Parentheses/file_1.py
new file mode 100644
index 000000000000..ed3de6593a59
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Parentheses/file_1.py
@@ -0,0 +1,20 @@
+import main
+
+def target_func():
+ main.nothing(None)
+
+
+def nothing(x):
+ pass
+
+
+def foo(x=bar()):
+ ((target_func))()
+
+
+def bar():
+ main.nothing((target_func))
+
+
+def another():
+ ((((target_func), 1))(), 2)()
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Parentheses/main.py b/python/testData/hierarchy/call/Static/Parentheses/main.py
new file mode 100644
index 000000000000..37fe5bf0819d
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Parentheses/main.py
@@ -0,0 +1,7 @@
+from file_1 import target_func
+
+def nothing(x):
+ pass
+
+
+target_func()
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Simple/Simple_callee_verification.xml b/python/testData/hierarchy/call/Static/Simple/Simple_callee_verification.xml
new file mode 100644
index 000000000000..54dd51a71979
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Simple/Simple_callee_verification.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Simple/Simple_caller_verification.xml b/python/testData/hierarchy/call/Static/Simple/Simple_caller_verification.xml
new file mode 100644
index 000000000000..7237852a9a40
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Simple/Simple_caller_verification.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Simple/main.py b/python/testData/hierarchy/call/Static/Simple/main.py
new file mode 100644
index 000000000000..9073f3df5d87
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Simple/main.py
@@ -0,0 +1,10 @@
+def func1():
+ pass
+
+def func2():
+ target_func()
+
+def target_func():
+ func1()
+
+target_func()
diff --git a/python/testSrc/com/jetbrains/python/hierarchy/HierarchyTreeStructureViewer.java b/python/testSrc/com/jetbrains/python/hierarchy/HierarchyTreeStructureViewer.java
new file mode 100644
index 000000000000..08d09f061d2b
--- /dev/null
+++ b/python/testSrc/com/jetbrains/python/hierarchy/HierarchyTreeStructureViewer.java
@@ -0,0 +1,160 @@
+/*
+ * 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.hierarchy;
+
+
+import com.intellij.ide.hierarchy.HierarchyNodeDescriptor;
+import com.intellij.ide.hierarchy.HierarchyTreeStructure;
+import junit.framework.TestCase;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+/**
+ * @author novokrest
+ * @see {@link com.intellij.testFramework.codeInsight.hierarchy.HierarchyViewTestBase}
+ */
+public abstract class HierarchyTreeStructureViewer extends TestCase {
+
+ private static final String NODE_ELEMENT_NAME = "node";
+ private static final String ANY_NODES_ELEMENT_NAME = "any";
+ private static final String TEXT_ATTR_NAME = "text";
+ private static final String BASE_ATTR_NAME = "base";
+
+ /**
+ * @see {@link com.intellij.testFramework.codeInsight.hierarchy.HierarchyViewTestBase}
+ */
+ public static void checkHierarchyTreeStructure(final HierarchyTreeStructure treeStructure, final Document document) {
+ final HierarchyNodeDescriptor rootNodeDescriptor = (HierarchyNodeDescriptor)treeStructure.getRootElement();
+ rootNodeDescriptor.update();
+ final Element rootElement = document.getRootElement();
+ if (rootElement == null || !NODE_ELEMENT_NAME.equals(rootElement.getName())) {
+ throw new IllegalArgumentException("Incorrect root element in verification resource");
+ }
+ checkNodeDescriptorRecursively(treeStructure, rootNodeDescriptor, rootElement);
+ }
+
+ /**
+ * @see {@link com.intellij.testFramework.codeInsight.hierarchy.HierarchyViewTestBase}
+ */
+ private static void checkNodeDescriptorRecursively(final HierarchyTreeStructure treeStructure,
+ final HierarchyNodeDescriptor descriptor,
+ final Element expectedElement) {
+ checkBaseNode(treeStructure, descriptor, expectedElement);
+ checkContent(descriptor, expectedElement);
+ checkChildren(treeStructure, descriptor, expectedElement);
+ }
+
+ /**
+ * @see {@link com.intellij.testFramework.codeInsight.hierarchy.HierarchyViewTestBase}
+ */
+ private static void checkBaseNode(final HierarchyTreeStructure treeStructure,
+ final HierarchyNodeDescriptor descriptor,
+ final Element expectedElement) {
+ final String baseAttrValue = expectedElement.getAttributeValue(BASE_ATTR_NAME);
+ final HierarchyNodeDescriptor baseDescriptor = treeStructure.getBaseDescriptor();
+ final boolean mustBeBase = "true".equalsIgnoreCase(baseAttrValue);
+ assertTrue("Incorrect base node", mustBeBase ? baseDescriptor == descriptor : baseDescriptor != descriptor);
+ }
+
+ /**
+ * @see {@link com.intellij.testFramework.codeInsight.hierarchy.HierarchyViewTestBase}
+ */
+ private static void checkContent(final HierarchyNodeDescriptor descriptor, final Element expectedElement) {
+ assertEquals(expectedElement.getAttributeValue(TEXT_ATTR_NAME), descriptor.getHighlightedText().getText());
+ }
+
+ /**
+ * @see {@link com.intellij.testFramework.codeInsight.hierarchy.HierarchyViewTestBase}
+ */
+ private static void checkChildren(final HierarchyTreeStructure treeStructure,
+ final HierarchyNodeDescriptor descriptor,
+ final Element element) {
+ if (element.getChild(ANY_NODES_ELEMENT_NAME) != null) {
+ return;
+ }
+
+ final Object[] children = treeStructure.getChildElements(descriptor);
+ //noinspection unchecked
+ final List expectedChildren = new ArrayList(element.getChildren(NODE_ELEMENT_NAME));
+
+ final StringBuilder messageBuilder = new StringBuilder("Actual children of [" + descriptor.getHighlightedText().getText() + "]:\n");
+ for (Object child : children) {
+ final HierarchyNodeDescriptor nodeDescriptor = (HierarchyNodeDescriptor)child;
+ nodeDescriptor.update();
+ messageBuilder.append(" [").append(nodeDescriptor.getHighlightedText().getText()).append("]\n");
+ }
+ assertEquals(messageBuilder.toString(), expectedChildren.size(), children.length);
+
+ Arrays.sort(children, new Comparator