blob: de9f7bf5a5ca04dfc3c72a0ad5ba8426cd3bbd0e [file] [log] [blame]
/*
* 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.structureView;
import com.intellij.ide.structureView.StructureViewTreeElement;
import com.intellij.navigation.ColoredItemPresentation;
import com.intellij.navigation.ItemPresentation;
import com.intellij.openapi.editor.colors.CodeInsightColors;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.ui.LayeredIcon;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.*;
import icons.PythonIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.*;
/**
* Handles nodes in Structure View.
* @author yole
*/
public class PyStructureViewElement implements StructureViewTreeElement {
protected enum Visibility {
NORMAL, // visible
INVISIBLE, // not visible: e.g. local to function
PRIVATE, // "__foo" in a class
PROTECTED, // "_foo"
PREDEFINED // like "__init__"; only if really visible
}
private PyElement myElement;
private Visibility myVisibility;
private Icon myIcon;
private boolean myInherited;
private boolean myField;
protected PyStructureViewElement(PyElement element, Visibility visibility, boolean inherited, boolean field) {
myElement = element;
myVisibility = visibility;
myInherited = inherited;
myField = field;
}
public PyStructureViewElement(PyElement element) {
this(element, Visibility.NORMAL, false, false);
}
protected StructureViewTreeElement createChild(PyElement element, Visibility visibility, boolean inherited, boolean field) {
return new PyStructureViewElement(element, visibility, inherited, field);
}
@Override
public PyElement getValue() {
return myElement;
}
public boolean isInherited() {
return myInherited;
}
public boolean isField() {
return myField;
}
@Override
public void navigate(boolean requestFocus) {
myElement.navigate(requestFocus);
}
@Override
public boolean canNavigate() {
return myElement.canNavigate();
}
@Override
public boolean canNavigateToSource() {
return myElement.canNavigateToSource();
}
public void setIcon(Icon icon) {
myIcon = icon;
}
@Override
public boolean equals(Object o) {
if (o instanceof StructureViewTreeElement) {
final Object value = ((StructureViewTreeElement)o).getValue();
final String name = myElement.getName();
if (value instanceof PyElement && name != null) {
return name.equals(((PyElement)value).getName());
}
}
return false;
}
@Override
public int hashCode() {
final String name = myElement.getName();
return name != null ? name.hashCode() : 0;
}
@NotNull
public StructureViewTreeElement[] getChildren() {
final Collection<StructureViewTreeElement> children = new ArrayList<StructureViewTreeElement>();
for (PyElement e : getElementChildren(myElement)) {
children.add(createChild(e, getElementVisibility(e), false, elementIsField(e)));
}
if (myElement instanceof PyClass && myElement.isValid()) {
for (PyClass c : ((PyClass)myElement).getAncestorClasses()) {
for (PyElement e: getElementChildren(c)) {
final StructureViewTreeElement inherited = createChild(e, getElementVisibility(e), true, elementIsField(e));
if (!children.contains(inherited)) {
children.add(inherited);
}
}
}
}
return children.toArray(new StructureViewTreeElement[children.size()]);
}
protected boolean elementIsField(PyElement element) {
return element instanceof PyTargetExpression && PsiTreeUtil.getParentOfType(element, PyClass.class) != null;
}
private static Visibility getElementVisibility(PyElement element) {
if (!(element instanceof PyTargetExpression) && PsiTreeUtil.getParentOfType(element, PyFunction.class) != null) {
return Visibility.INVISIBLE;
}
else {
return getVisibilityByName(element.getName());
}
}
private Collection<PyElement> getElementChildren(final PyElement element) {
final Collection<PyElement> children = new ArrayList<PyElement>();
if (!element.isValid()) {
return children;
}
element.acceptChildren(new PyElementVisitor() {
@Override
public void visitElement(PsiElement e) {
if (isWorthyItem(e, element)) {
children.add((PyElement)e);
}
else {
e.acceptChildren(this);
}
}
});
if (element instanceof PyClass) {
final List<PyElement> attrs = getClassAttributes((PyClass)element);
final List<PyElement> filtered = new ArrayList<PyElement>();
final Set<String> names = new HashSet<String>();
for (PyElement attr : attrs) {
final String name = attr.getName();
if (attr.isValid() && !names.contains(name)) {
filtered.add(attr);
}
names.add(name);
}
final Comparator<PyElement> comparator = new Comparator<PyElement>() {
@Override
public int compare(PyElement e1, PyElement e2) {
final String n1 = e1.getName();
final String n2 = e2.getName();
return (n1 != null && n2 != null) ? n1.compareTo(n2) : 0;
}
};
Collections.sort(filtered, comparator);
children.addAll(filtered);
}
return children;
}
protected List<PyElement> getClassAttributes(PyClass cls) {
final List<PyElement> results = new ArrayList<PyElement>();
results.addAll(cls.getInstanceAttributes());
results.addAll(cls.getClassAttributes());
return results;
}
private static Visibility getVisibilityByName(@Nullable String name) {
if (name != null) {
if (name.startsWith("__")) {
if (PyNames.UnderscoredAttributes.contains(name)) {
return Visibility.PREDEFINED;
}
else {
return Visibility.PRIVATE;
}
}
else if (name.startsWith("_")) {
return Visibility.PROTECTED;
}
}
return Visibility.NORMAL;
}
protected boolean isWorthyItem(@Nullable PsiElement element, @Nullable PsiElement parent) {
if (element instanceof PyClass || element instanceof PyFunction) {
return true;
}
if (!(parent instanceof PyClass) && (element instanceof PyTargetExpression) && !((PyTargetExpression)element).isQualified()) {
PsiElement e = element.getParent();
if (e instanceof PyAssignmentStatement) {
e = e.getParent();
if (e instanceof PyFile) {
return true;
}
}
}
return false;
}
@NotNull
@Override
public ItemPresentation getPresentation() {
final ItemPresentation presentation = myElement.getPresentation();
return new ColoredItemPresentation() {
@Nullable
@Override
public String getPresentableText() {
if (myElement instanceof PyFile) {
return myElement.getName();
}
return presentation != null ? presentation.getPresentableText() : PyNames.UNNAMED_ELEMENT;
}
@Nullable
@Override
public TextAttributesKey getTextAttributesKey() {
if (isInherited()) {
return CodeInsightColors.NOT_USED_ELEMENT_ATTRIBUTES;
}
return null;
}
@Nullable
@Override
public String getLocationString() {
return null;
}
@Nullable
@Override
public Icon getIcon(boolean open) {
Icon normal_icon = myElement.getIcon(0);
if (myIcon != null) normal_icon = myIcon; // override normal
if (myVisibility == Visibility.NORMAL) {
return normal_icon;
}
else {
LayeredIcon icon = new LayeredIcon(2);
icon.setIcon(normal_icon, 0);
Icon overlay = null;
if (myVisibility == Visibility.PRIVATE || myVisibility == Visibility.PROTECTED) {
overlay = PythonIcons.Python.Nodes.Lock;
}
else if (myVisibility == Visibility.PREDEFINED) {
overlay = PythonIcons.Python.Nodes.Cyan_dot;
}
else if (myVisibility == Visibility.INVISIBLE) {
overlay = PythonIcons.Python.Nodes.Red_inv_triangle;
}
if (overlay != null) {
icon.setIcon(overlay, 1);
}
return icon;
}
}
};
}
}