Merge remote-tracking branch 'origin/master'

This commit is contained in:
Dmitry Trofimov
2014-09-02 17:45:02 +02:00
54 changed files with 1608 additions and 102 deletions
@@ -15,7 +15,7 @@
*/
package org.jetbrains.idea.devkit.dom.impl;
import com.intellij.ide.plugins.PluginManager;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
@@ -50,57 +50,6 @@ import java.util.*;
public class ExtensionDomExtender extends DomExtender<Extensions> {
private static final PsiClassConverter CLASS_CONVERTER = new PluginPsiClassConverter();
private static final Converter LANGUAGE_CONVERTER = new LanguageResolvingConverter();
private static class MyRequired implements Required {
@Override
public boolean value() {
return true;
}
@Override
public boolean nonEmpty() {
return true;
}
@Override
public boolean identifier() {
return false;
}
@Override
public Class<? extends Annotation> annotationType() {
return Required.class;
}
}
private static class MyExtendClass extends ExtendClassImpl {
private final String myInterfaceName;
private MyExtendClass(String interfaceName) {
myInterfaceName = interfaceName;
}
@Override
public boolean allowAbstract() {
return false;
}
@Override
public boolean allowInterface() {
return false;
}
@Override
public boolean allowEnum() {
return false;
}
@Override
public String value() {
return myInterfaceName;
}
}
private static final DomExtender EXTENSION_EXTENDER = new DomExtender() {
public void registerExtensions(@NotNull final DomElement domElement, @NotNull final DomExtensionsRegistrar registrar) {
final ExtensionPoint extensionPoint = (ExtensionPoint)domElement.getChildDescription().getDomDeclaration();
@@ -127,31 +76,6 @@ public class ExtensionDomExtender extends DomExtender<Extensions> {
}
};
public void registerExtensions(@NotNull final Extensions extensions, @NotNull final DomExtensionsRegistrar registrar) {
final XmlElement xmlElement = extensions.getXmlElement();
if (xmlElement == null) return;
IdeaPlugin ideaPlugin = extensions.getParentOfType(IdeaPlugin.class, true);
if (ideaPlugin == null) return;
String epPrefix = extensions.getEpPrefix();
for (IdeaPlugin plugin : getVisiblePlugins(ideaPlugin)) {
final String pluginId = StringUtil.notNullize(plugin.getPluginId(), "com.intellij");
for (ExtensionPoints points : plugin.getExtensionPoints()) {
for (ExtensionPoint point : points.getExtensionPoints()) {
registerExtensionPoint(registrar, point, epPrefix, pluginId);
}
}
}
}
@Override
public boolean supportsStubs() {
return false;
}
private static Set<IdeaPlugin> getVisiblePlugins(IdeaPlugin ideaPlugin) {
Set<IdeaPlugin> result = ContainerUtil.newHashSet();
MultiMap<String, IdeaPlugin> byId = getPluginMap(ideaPlugin.getManager().getProject());
@@ -385,11 +309,10 @@ public class ExtensionDomExtender extends DomExtender<Extensions> {
return PsiTypesUtil.getPsiClass(elementType);
}
public static Collection<String> getDependencies(IdeaPlugin ideaPlugin) {
Set<String> result = new HashSet<String>();
result.add(PluginManager.CORE_PLUGIN_ID);
result.add(PluginManagerCore.CORE_PLUGIN_ID);
for (Dependency dependency : ideaPlugin.getDependencies()) {
ContainerUtil.addIfNotNull(dependency.getStringValue(), result);
@@ -422,10 +345,84 @@ public class ExtensionDomExtender extends DomExtender<Extensions> {
return result;
}
public void registerExtensions(@NotNull final Extensions extensions, @NotNull final DomExtensionsRegistrar registrar) {
final XmlElement xmlElement = extensions.getXmlElement();
if (xmlElement == null) return;
IdeaPlugin ideaPlugin = extensions.getParentOfType(IdeaPlugin.class, true);
if (ideaPlugin == null) return;
String epPrefix = extensions.getEpPrefix();
for (IdeaPlugin plugin : getVisiblePlugins(ideaPlugin)) {
final String pluginId = StringUtil.notNullize(plugin.getPluginId(), "com.intellij");
for (ExtensionPoints points : plugin.getExtensionPoints()) {
for (ExtensionPoint point : points.getExtensionPoints()) {
registerExtensionPoint(registrar, point, epPrefix, pluginId);
}
}
}
}
@Override
public boolean supportsStubs() {
return false;
}
interface SimpleTagValue extends DomElement {
@SuppressWarnings("UnusedDeclaration")
@TagValue
String getTagValue();
}
private static class MyRequired implements Required {
@Override
public boolean value() {
return true;
}
@Override
public boolean nonEmpty() {
return true;
}
@Override
public boolean identifier() {
return false;
}
@Override
public Class<? extends Annotation> annotationType() {
return Required.class;
}
}
private static class MyExtendClass extends ExtendClassImpl {
private final String myInterfaceName;
private MyExtendClass(String interfaceName) {
myInterfaceName = interfaceName;
}
@Override
public boolean allowAbstract() {
return false;
}
@Override
public boolean allowInterface() {
return false;
}
@Override
public boolean allowEnum() {
return false;
}
@Override
public String value() {
return myInterfaceName;
}
}
}
@@ -0,0 +1,5 @@
<idea-plugin>
<extensionPoints>
<extensionPoint name="extension" beanClass="foo.Bar"/>
</extensionPoints>
</idea-plugin>
@@ -0,0 +1,10 @@
<idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude" version="2">
<id>com.intellij</id>
<extensionPoints>
<extensionPoint name="bar"/>
<xi:include href="/extensionPoints.xml" xpointer="xpointer(/extensionPoints/*)"/>
</extensionPoints>
</idea-plugin>
@@ -0,0 +1,15 @@
<idea-plugin version="2">
<id>foo</id>
<extensionPoints>
<extensionPoint name="bar"/>
</extensionPoints>
<extensions defaultExtensionNs="foo">
<bar/>
<extension/>
</extensions>
</idea-plugin>
@@ -44,6 +44,13 @@ public class PluginXmlDomStubsTest extends DomStubTest {
);
}
public void testXInclude() throws Exception {
prepareFile("pluginWithXInclude-extensionPoints.xml");
prepareFile("pluginWithXInclude-main.xml");
prepareFile("pluginWithXInclude.xml");
myFixture.testHighlighting("pluginWithXInclude.xml");
}
@Override
protected String getBasePath() {
return PluginPathManager.getPluginHomePathRelative("devkit") + "/testData/pluginXmlDomStubs";
+26
View File
@@ -137,6 +137,7 @@
<gotoTargetRendererProvider implementation="com.jetbrains.python.codeInsight.PyGotoTargetRendererProvider"/>
<typeHierarchyProvider language="Python" implementationClass="com.jetbrains.python.hierarchy.PyTypeHierachyProvider"/>
<callHierarchyProvider language="Python" implementationClass="com.jetbrains.python.hierarchy.call.PyCallHierarchyProvider"/>
<highlightUsagesHandlerFactory implementation="com.jetbrains.python.codeInsight.highlighting.PyHighlightExitPointsHandlerFactory"/>
<joinLinesHandler implementation="com.jetbrains.python.editor.PyJoinLinesHandler"/>
@@ -476,6 +477,9 @@
<projectService serviceInterface="com.jetbrains.python.debugger.PySignatureCacheManager"
serviceImplementation="com.jetbrains.python.debugger.PySignatureCacheManagerImpl"/>
<projectService serviceInterface="com.jetbrains.python.hierarchy.call.PyStaticCallDataManager"
serviceImplementation="com.jetbrains.python.hierarchy.call.PyStaticCallDataManagerImpl"/>
<projectService serviceInterface="com.jetbrains.python.psi.PyPsiFacade"
serviceImplementation="com.jetbrains.python.psi.impl.PyPsiFacadeImpl"/>
<applicationService serviceInterface="com.jetbrains.python.packaging.PyPackageManagers"
@@ -647,7 +651,29 @@
<reference ref="CompareFileWithEditor"/>
</group>
<group id="PyCallHierarchyPopupMenu">
<action id="CallHierarchy.BaseOnThisType" text="Base On This Function" class="com.jetbrains.python.hierarchy.call.PyCallHierarchyBrowser$BaseOnThisFunctionAction" description="Show call hierarchy for selected method"/>
<reference ref="EditSource"/>
<separator/>
<reference ref="FindUsages"/>
<reference ref="RefactoringMenu"/>
<separator/>
<reference ref="AddToFavorites"/>
<separator/>
<reference ref="RunContextPopupGroup"/>
<separator/>
<reference ref="ReformatCode"/>
<reference ref="OptimizeImports"/>
<separator/>
<reference ref="VersionControlsGroup"/>
<separator/>
<reference ref="ExternalToolsGroup"/>
<separator/>
<reference ref="CompareTwoFiles"/>
<reference ref="CompareFileWithEditor"/>
</group>
<action id="com.jetbrains.python.console.PyOpenDebugConsoleAction"
class="com.jetbrains.python.console.PyOpenDebugConsoleAction"
@@ -27,12 +27,12 @@ import java.util.List;
public class PyFunctionFindUsagesHandler extends FindUsagesHandler {
private final List<PsiElement> myAllElements;
protected PyFunctionFindUsagesHandler(@NotNull PsiElement psiElement) {
public PyFunctionFindUsagesHandler(@NotNull PsiElement psiElement) {
super(psiElement);
myAllElements = null;
}
protected PyFunctionFindUsagesHandler(@NotNull PsiElement psiElement, List<PsiElement> allElements) {
public PyFunctionFindUsagesHandler(@NotNull PsiElement psiElement, List<PsiElement> allElements) {
super(psiElement);
myAllElements = allElements;
}
@@ -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<PsiElement> getCallees(@NotNull PyElement pyElement);
Collection<PsiElement> getCallers(@NotNull PyElement pyElement);
}
@@ -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<String, JTree> 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<NodeDescriptor> getComparator() {
return PyHierarchyUtils.getComparator(myProject);
}
public static final class BaseOnThisFunctionAction extends CallHierarchyBrowserBase.BaseOnThisMethodAction {
}
}
@@ -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<PsiReference> myReferences = new ArrayList<PsiReference>();
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<RangeHighlighter> highlighters = new ArrayList<RangeHighlighter>();
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();
}
}
@@ -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);
}
}
@@ -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<PsiElement> 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<PsiElement, PyCallHierarchyNodeDescriptor> calleeToDescriptorMap = new HashMap<PsiElement, PyCallHierarchyNodeDescriptor>();
final List<PyCallHierarchyNodeDescriptor> 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;
}
}
@@ -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<PsiElement> 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<PsiElement, PyCallHierarchyNodeDescriptor> callerToDescriptorMap = new HashMap<PsiElement, PyCallHierarchyNodeDescriptor>();
final List<PyCallHierarchyNodeDescriptor> 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;
}
}
@@ -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);
}
}
@@ -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<PsiElement> getCallees(@NotNull PyElement element) {
final List<PsiElement> 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<PsiElement> getCallers(@NotNull PyElement pyElement) {
final List<PsiElement> callers = Lists.newArrayList();
final Collection<UsageInfo> 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<UsageInfo> findUsages(@NotNull final PsiElement element) {
final FindUsagesHandler handler = createFindUsageHandler(element);
if (handler == null) {
return Lists.newArrayList();
}
final CommonProcessors.CollectProcessor<UsageInfo> processor = new CommonProcessors.CollectProcessor<UsageInfo>();
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<PsiElement> 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<PsiElement> 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)));
}
}
@@ -0,0 +1 @@
<node text="target_func(file_1.py)" base="true"/>
@@ -0,0 +1 @@
<node text="target_func(file_1.py)" base="true"/>
@@ -0,0 +1,8 @@
def target_func():
pass
def func1(f):
f()
def func2(f):
return f
@@ -0,0 +1,8 @@
from file_1 import *
target_<caret>func()
func1(target_func)
func1(target_func())
func2(target_func)
func2(target_func())
@@ -0,0 +1,8 @@
<node text="A.__init__(main.py)" base="true">
<node text="invoke1(main.py)">
<node text="A.method1(main.py)"/>
</node>
<node text="invoke2(main.py)">
<node text="A.method2(main.py)"/>
</node>
</node>
@@ -0,0 +1,6 @@
<node text="A.__init__(main.py)" base="true">
<node text="invokeA(main.py)">
<node text="C.bar(main.py)"/>
</node>
<node text="C.bar(main.py)"/>
</node>
@@ -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_<caret>_(a)
@@ -0,0 +1 @@
<node text="target_func(main.py)" base="true"/>
@@ -0,0 +1,4 @@
<node text="target_func(main.py)" base="true">
<node text="func2(main.py)"/>
<node text="func4(main.py)"/>
</node>
@@ -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_<caret>func()
@@ -0,0 +1 @@
<node text="A.target_func(main.py)" base="true"/>
@@ -0,0 +1,4 @@
<node text="A.target_func(main.py)" base="true">
<node text="C.func(main.py)"/>
<node text="foo2(main.py)"/>
</node>
@@ -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_<caret>func)
bar2(b.target_func())
@@ -0,0 +1,5 @@
<node text="target_func(main.py)" base="true">
<node text="foo(main.py)">
<node text="bar(main.py)"/>
</node>
</node>
@@ -0,0 +1 @@
<node text="target_func(main.py)" base="true"/>
@@ -0,0 +1,9 @@
def bar():
pass
def target_func():
def foo():
return bar()
foo()
target_<caret>func()
@@ -0,0 +1,8 @@
<node text="target_func(main.py)" base="true">
<node text="func8(file_1.py)"/>
<node text="func13(file_1.py)"/>
<node text="func15(file_1.py)"/>
<node text="inner(main.py)">
<node text="func11(file_1.py)"/>
</node>
</node>
@@ -0,0 +1 @@
<node text="target_func(main.py)" base="true"/>
@@ -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
@@ -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_<caret>func()
@@ -0,0 +1,12 @@
<node text="target_func(main.py)" base="true">
<node text="func5(file_1.py)"/>
<node text="func3(file_1.py)"/>
<node text="func6(file_1.py)"/>
<node text="func7(file_1.py)"/>
<node text="func2(file_1.py)"/>
<node text="func1(file_1.py)"/>
<node text="func10(file_1.py)"/>
<node text="inner(main.py)">
<node text="func1(file_1.py)"/>
</node>
</node>
@@ -0,0 +1 @@
<node text="target_func(main.py)" base="true"/>
@@ -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
@@ -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_<caret>func()
@@ -0,0 +1,3 @@
<node text="B.target_func(main.py)" base="true">
<node text="A.another_func(file_1.py)"/>
</node>
@@ -0,0 +1,5 @@
<node text="B.target_func(main.py)" base="true">
<node text="C.func1(main.py)"/>
<node text="bar1(main.py)"/>
<node text="C.func2(main.py)"/>
</node>
@@ -0,0 +1,8 @@
class A(object):
def target_func(self, p):
pass
def another_func(self):
pass
@@ -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_<caret>func(A())
@@ -0,0 +1,3 @@
<node text="target_func(file_1.py)" base="true">
<node text="nothing(main.py)"/>
</node>
@@ -0,0 +1,3 @@
<node text="target_func(file_1.py)" base="true">
<node text="foo(file_1.py)"/>
</node>
@@ -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)()
@@ -0,0 +1,7 @@
from file_1 import target_func
def nothing(x):
pass
target_<caret>func()
@@ -0,0 +1,3 @@
<node text="target_func(main.py)" base="true">
<node text="func1(main.py)"/>
</node>
@@ -0,0 +1,3 @@
<node text="target_func(main.py)" base="true">
<node text="func2(main.py)"/>
</node>
@@ -0,0 +1,10 @@
def func1():
pass
def func2():
target_func()
def target_func():
func1()
target_<caret>func()
@@ -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<Element> expectedChildren = new ArrayList<Element>(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<Object>() {
@Override
public int compare(final Object first, final Object second) {
return ((HierarchyNodeDescriptor)first).getHighlightedText().getText()
.compareTo(((HierarchyNodeDescriptor)second).getHighlightedText().getText());
}
});
Collections.sort(expectedChildren, new Comparator<Element>() {
@Override
public int compare(final Element first, final Element second) {
return first.getAttributeValue(TEXT_ATTR_NAME).compareTo(second.getAttributeValue(TEXT_ATTR_NAME));
}
});
//noinspection unchecked
final Iterator<Element> iterator = expectedChildren.iterator();
for (Object child : children) {
checkNodeDescriptorRecursively(treeStructure, ((HierarchyNodeDescriptor)child), iterator.next());
}
}
public static String dump(final HierarchyTreeStructure treeStructure, @Nullable HierarchyNodeDescriptor descriptor, int level) {
StringBuilder s = new StringBuilder();
dump(treeStructure, descriptor, level, s);
return s.toString();
}
private static void dump(final HierarchyTreeStructure treeStructure,
@Nullable HierarchyNodeDescriptor descriptor,
int level,
StringBuilder b) {
if (level > 10) {
for(int i = 0; i<level; i++) b.append(" ");
b.append("<Probably infinite part skipped>\n");
return;
}
if(descriptor==null) descriptor = (HierarchyNodeDescriptor)treeStructure.getRootElement();
for(int i = 0; i<level; i++) b.append(" ");
descriptor.update();
b.append("<node text=\"").append(descriptor.getHighlightedText().getText()).append("\"")
.append(treeStructure.getBaseDescriptor() == descriptor ? " base=\"true\"" : "");
final Object[] children = treeStructure.getChildElements(descriptor);
if(children.length>0) {
b.append(">\n");
for (Object o : children) {
HierarchyNodeDescriptor d = (HierarchyNodeDescriptor)o;
dump(treeStructure, d, level + 1, b);
}
for(int i = 0; i<level; i++) b.append(" ");
b.append("</node>\n");
} else {
b.append("/>\n");
}
}
}
@@ -0,0 +1,121 @@
/*
* 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.codeInsight.TargetElementUtilBase;
import com.intellij.ide.hierarchy.HierarchyBrowserBaseEx;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.psi.PsiElement;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.hierarchy.call.PyCalleeFunctionTreeStructure;
import com.jetbrains.python.hierarchy.call.PyCallerFunctionTreeStructure;
import com.jetbrains.python.psi.PyFunction;
import java.io.File;
/**
* @author novokrest
*/
public class PyCallHierarchyTest extends PyTestCase {
private static final String CALLER_VERIFICATION_SUFFIX = "_caller_verification.xml";
private static final String CALLEE_VERIFICATION_SUFFIX = "_callee_verification.xml";
private String getBasePath() {
return "hierarchy/call/Static/" + getTestName(false);
}
private void configureByFiles(String ... fileNames) {
String[] filePaths = new String[fileNames.length];
int i = 0;
for (String fileName: fileNames) {
filePaths[i] = getBasePath() + "/" + fileName;
i++;
}
myFixture.configureByFiles(filePaths);
}
private String getVerificationFilePath(final String suffix) {
return getTestDataPath() + "/" + getBasePath() + "/" + getTestName(false) + suffix;
}
private String getVerificationCallerFilePath() {
return getVerificationFilePath(CALLER_VERIFICATION_SUFFIX);
}
private String getVerificationCalleeFilePath() {
return getVerificationFilePath(CALLEE_VERIFICATION_SUFFIX);
}
private void checkFunctionHierarchyTreeStructure(PyFunction function) throws Exception {
HierarchyTreeStructureViewer.checkHierarchyTreeStructure(new PyCallerFunctionTreeStructure(myFixture.getProject(), function, HierarchyBrowserBaseEx.SCOPE_PROJECT),
JDOMUtil.loadDocument(new File(getVerificationCallerFilePath())));
HierarchyTreeStructureViewer.checkHierarchyTreeStructure(new PyCalleeFunctionTreeStructure(myFixture.getProject(), function, HierarchyBrowserBaseEx.SCOPE_PROJECT),
JDOMUtil.loadDocument(new File(getVerificationCalleeFilePath())));
}
private void doTestCallHierarchy(String ... fileNames) throws Exception {
configureByFiles(fileNames);
final PsiElement targetElement = TargetElementUtilBase
.findTargetElement(myFixture.getEditor(),
TargetElementUtilBase.ELEMENT_NAME_ACCEPTED | TargetElementUtilBase.REFERENCED_ELEMENT_ACCEPTED);
assert targetElement != null : "Cannot find referenced element";
assert targetElement instanceof PyFunction : "Referenced element is not PyFunction";
PyFunction function = (PyFunction) targetElement;
checkFunctionHierarchyTreeStructure(function);
}
public void testSimple() throws Exception {
doTestCallHierarchy("main.py");
}
public void testArgumentList() throws Exception {
doTestCallHierarchy("main.py", "file_1.py");
}
public void testDefaultValue() throws Exception {
doTestCallHierarchy("main.py");
}
public void testLambda() throws Exception {
doTestCallHierarchy("main.py", "file_1.py");
}
public void testNestedCall() throws Exception {
doTestCallHierarchy("main.py", "file_1.py");
}
public void testInheritance() throws Exception {
doTestCallHierarchy("main.py");
}
public void testOverriddenMethod() throws Exception {
doTestCallHierarchy("main.py", "file_1.py");
}
public void testInnerFunction() throws Exception {
doTestCallHierarchy("main.py");
}
public void testConstructor() throws Exception {
doTestCallHierarchy("main.py");
}
public void testParentheses() throws Exception {
doTestCallHierarchy("main.py", "file_1.py");
}
}
@@ -20,6 +20,7 @@ import com.intellij.psi.xml.XmlElement;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.xml.impl.*;
import com.intellij.xml.util.XmlUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -32,6 +33,11 @@ import java.util.List;
public class StubParentStrategy implements DomParentStrategy {
private final static Logger LOG = Logger.getInstance(StubParentStrategy.class);
protected final DomStub myStub;
public StubParentStrategy(@NotNull DomStub stub) {
myStub = stub;
}
public static StubParentStrategy createAttributeStrategy(@Nullable AttributeStub stub, @NotNull final DomStub parent) {
if (stub == null) {
@@ -59,12 +65,6 @@ public class StubParentStrategy implements DomParentStrategy {
}
}
protected final DomStub myStub;
public StubParentStrategy(@NotNull DomStub stub) {
myStub = stub;
}
@Override
public DomInvocationHandler getParentHandler() {
DomStub parentStub = myStub.getParentStub();
@@ -82,7 +82,14 @@ public class StubParentStrategy implements DomParentStrategy {
// for custom elements, namespace information is lost
// todo: propagate ns info through DomChildDescriptions
XmlTag[] tags = parentTag.getSubTags();
XmlTag[] tags;
try {
XmlUtil.BUILDING_DOM_STUBS.set(true);
tags = parentTag.getSubTags();
}
finally {
XmlUtil.BUILDING_DOM_STUBS.set(false);
}
int i = 0;
String nameToFind = myStub.getName();
@@ -52,6 +52,19 @@ public abstract class DomStubTest extends LightCodeInsightFixtureTestCase {
}
};
public static ElementStub getRootStub(@TestDataFile String filePath, JavaCodeInsightTestFixture fixture) {
PsiFile psiFile = fixture.configureByFile(filePath);
StubTreeLoader loader = StubTreeLoader.getInstance();
VirtualFile file = psiFile.getVirtualFile();
assertTrue(loader.canHaveStub(file));
ObjectStubTree stubTree = loader.readFromVFile(fixture.getProject(), file);
assertNotNull(stubTree);
ElementStub root = (ElementStub)stubTree.getRoot();
assertNotNull(root);
return root;
}
@Override
public void setUp() throws Exception {
super.setUp();
@@ -67,19 +80,6 @@ public abstract class DomStubTest extends LightCodeInsightFixtureTestCase {
return getRootStub(filePath, myFixture);
}
public static ElementStub getRootStub(@TestDataFile String filePath, JavaCodeInsightTestFixture fixture) {
PsiFile psiFile = fixture.configureByFile(filePath);
StubTreeLoader loader = StubTreeLoader.getInstance();
VirtualFile file = psiFile.getVirtualFile();
assertTrue(loader.canHaveStub(file));
ObjectStubTree stubTree = loader.readFromVFile(fixture.getProject(), file);
assertNotNull(stubTree);
ElementStub root = (ElementStub)stubTree.getRoot();
assertNotNull(root);
return root;
}
protected void doBuilderTest(@TestDataFile String file, String stubText) {
ElementStub stub = getRootStub(file);
assertEquals(stubText, DebugUtil.stubTreeToString(stub));
@@ -99,7 +99,7 @@ public abstract class DomStubTest extends LightCodeInsightFixtureTestCase {
XmlFile file = (XmlFile)((PsiManagerEx)getPsiManager()).getFileManager().findFile(virtualFile);
assertFalse(file.getNode().isParsed());
ObjectStubTree tree = StubTreeLoader.getInstance().readOrBuild(getProject(), virtualFile, file);
assertNotNull(tree);
assertNotNull("Can't build stubs for " + path, tree);
((PsiManagerImpl)getPsiManager()).cleanupForNextTest();