EA-83566 PyStructureViewElement considers that underlying PSI element can be invalid
[idea/community.git] / python / src / com / jetbrains / python / structureView / PyStructureViewElement.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.jetbrains.python.structureView;
17
18 import com.intellij.ide.structureView.StructureViewTreeElement;
19 import com.intellij.navigation.ColoredItemPresentation;
20 import com.intellij.navigation.ItemPresentation;
21 import com.intellij.openapi.editor.colors.CodeInsightColors;
22 import com.intellij.openapi.editor.colors.TextAttributesKey;
23 import com.intellij.psi.PsiElement;
24 import com.intellij.psi.util.PsiTreeUtil;
25 import com.intellij.ui.LayeredIcon;
26 import com.jetbrains.python.PyNames;
27 import com.jetbrains.python.psi.*;
28 import com.jetbrains.python.psi.impl.PyPsiUtils;
29 import icons.PythonIcons;
30 import org.jetbrains.annotations.NotNull;
31 import org.jetbrains.annotations.Nullable;
32
33 import javax.swing.*;
34 import java.util.*;
35
36 /**
37  * Handles nodes in Structure View.
38  * @author yole
39  */
40 public class PyStructureViewElement implements StructureViewTreeElement {
41
42   protected enum Visibility {
43     NORMAL, // visible
44     INVISIBLE, // not visible: e.g. local to function
45     PRIVATE,  // "__foo" in a class
46     PROTECTED, // "_foo"
47     PREDEFINED // like "__init__"; only if really visible
48   }
49
50   private PyElement myElement;
51   private Visibility myVisibility;
52   private Icon myIcon;
53   private boolean myInherited;
54   private boolean myField;
55
56   protected PyStructureViewElement(PyElement element, Visibility visibility, boolean inherited, boolean field) {
57     myElement = element;
58     myVisibility = visibility;
59     myInherited = inherited;
60     myField = field;
61   }
62
63   public PyStructureViewElement(PyElement element) {
64     this(element, Visibility.NORMAL, false, false);
65   }
66
67   protected StructureViewTreeElement createChild(PyElement element, Visibility visibility, boolean inherited, boolean field) {
68     return new PyStructureViewElement(element, visibility, inherited, field);
69   }
70
71   @Nullable
72   @Override
73   public PyElement getValue() {
74     return myElement.isValid() ? myElement : null;
75   }
76
77   public boolean isInherited() {
78     return myInherited;
79   }
80
81   public boolean isField() {
82     return myField;
83   }
84
85   @Override
86   public void navigate(boolean requestFocus) {
87     final PyElement element = getValue();
88     if (element != null) {
89       myElement.navigate(requestFocus);
90     }
91   }
92
93   @Override
94   public boolean canNavigate() {
95     return myElement.isValid() && myElement.canNavigate();
96   }
97
98   @Override
99   public boolean canNavigateToSource() {
100     return myElement.isValid() && myElement.canNavigateToSource();
101   }
102
103   public void setIcon(Icon icon) {
104     myIcon = icon;
105   }
106
107   @Override
108   public boolean equals(Object o) {
109     if (o instanceof StructureViewTreeElement) {
110       final Object value = ((StructureViewTreeElement)o).getValue();
111       final String name = myElement.getName();
112       if (value instanceof PyElement && name != null) {
113         return name.equals(((PyElement)value).getName());
114       }
115     }
116     return false;
117   }
118
119   @Override
120   public int hashCode() {
121     final String name = myElement.getName();
122     return name != null ? name.hashCode() : 0;
123   }
124
125   @NotNull
126   public StructureViewTreeElement[] getChildren() {
127     final PyElement element = getValue();
128     if (element == null) {
129       return EMPTY_ARRAY;
130     }
131
132     final Collection<StructureViewTreeElement> children = new ArrayList<>();
133     for (PyElement e : getElementChildren(element)) {
134       children.add(createChild(e, getElementVisibility(e), false, elementIsField(e)));
135     }
136     PyPsiUtils.assertValid(element);
137     if (element instanceof PyClass) {
138       for (PyClass c : ((PyClass)element).getAncestorClasses(null)) {
139         for (PyElement e: getElementChildren(c)) {
140           final StructureViewTreeElement inherited = createChild(e, getElementVisibility(e), true, elementIsField(e));
141           if (!children.contains(inherited)) {
142             children.add(inherited);
143           }
144         }
145       }
146     }
147     return children.toArray(new StructureViewTreeElement[children.size()]);
148   }
149
150   protected boolean elementIsField(PyElement element) {
151     return element instanceof PyTargetExpression && PsiTreeUtil.getParentOfType(element, PyClass.class) != null;
152   }
153
154   private static Visibility getElementVisibility(PyElement element) {
155     if (!(element instanceof PyTargetExpression) && PsiTreeUtil.getParentOfType(element, PyFunction.class) != null) {
156       return Visibility.INVISIBLE;
157     }
158     else {
159       return getVisibilityByName(element.getName());
160     }
161   }
162
163   private Collection<PyElement> getElementChildren(final PyElement element) {
164     final Collection<PyElement> children = new ArrayList<>();
165     PyPsiUtils.assertValid(element);
166     element.acceptChildren(new PyElementVisitor() {
167       @Override
168       public void visitElement(PsiElement e) {
169         if (isWorthyItem(e, element)) {
170           children.add((PyElement)e);
171         }
172         else {
173           e.acceptChildren(this);
174         }
175       }
176     });
177     if (element instanceof PyClass) {
178       final List<PyElement> attrs = getClassAttributes((PyClass)element);
179       final List<PyElement> filtered = new ArrayList<>();
180       final Set<String> names = new HashSet<>();
181       for (PyElement attr : attrs) {
182         final String name = attr.getName();
183         PyPsiUtils.assertValid(attr);
184         if (!names.contains(name)) {
185           filtered.add(attr);
186         }
187         names.add(name);
188       }
189       final Comparator<PyElement> comparator = (e1, e2) -> {
190         final String n1 = e1.getName();
191         final String n2 = e2.getName();
192         return (n1 != null && n2 != null) ? n1.compareTo(n2) : 0;
193       };
194       Collections.sort(filtered, comparator);
195       children.addAll(filtered);
196     }
197     return children;
198   }
199
200   protected List<PyElement> getClassAttributes(PyClass cls) {
201     final List<PyElement> results = new ArrayList<>();
202     results.addAll(cls.getInstanceAttributes());
203     results.addAll(cls.getClassAttributes());
204     return results;
205   }
206
207   private static Visibility getVisibilityByName(@Nullable String name) {
208     if (name != null) {
209       if (name.startsWith("__")) {
210         if (PyNames.UnderscoredAttributes.contains(name)) {
211           return Visibility.PREDEFINED;
212         }
213         else {
214           return Visibility.PRIVATE;
215         }
216       }
217       else if (name.startsWith("_")) {
218         return Visibility.PROTECTED;
219       }
220     }
221     return Visibility.NORMAL;
222   }
223
224   protected boolean isWorthyItem(@Nullable PsiElement element, @Nullable PsiElement parent) {
225     if (element instanceof PyClass || element instanceof PyFunction) {
226       return true;
227     }
228     if (!(parent instanceof PyClass) && (element instanceof PyTargetExpression) && !((PyTargetExpression)element).isQualified()) {
229       PsiElement e = element.getParent();
230       if (e instanceof PyAssignmentStatement) {
231         e = e.getParent();
232         if (e instanceof PyFile) {
233           return true;
234         }
235       }
236     }
237     return false;
238   }
239
240   @NotNull
241   @Override
242   public ItemPresentation getPresentation() {
243     final PyElement element = getValue();
244     final ItemPresentation presentation = element != null ? element.getPresentation() : null;
245
246     return new ColoredItemPresentation() {
247       @Nullable
248       @Override
249       public String getPresentableText() {
250         if (element instanceof PyFile) {
251           return element.getName();
252         }
253         return presentation != null ? presentation.getPresentableText() : PyNames.UNNAMED_ELEMENT;
254       }
255
256       @Nullable
257       @Override
258       public TextAttributesKey getTextAttributesKey() {
259         if (isInherited()) {
260           return CodeInsightColors.NOT_USED_ELEMENT_ATTRIBUTES;
261         }
262         return null;
263       }
264
265       @Nullable
266       @Override
267       public String getLocationString() {
268         return null;
269       }
270
271       @Nullable
272       @Override
273       public Icon getIcon(boolean open) {
274         if (element == null) {
275           return null;
276         }
277
278         Icon normal_icon = element.getIcon(0);
279         if (myIcon != null) normal_icon = myIcon; // override normal
280         if (myVisibility == Visibility.NORMAL) {
281           return normal_icon;
282         }
283         else {
284           LayeredIcon icon = new LayeredIcon(2);
285           icon.setIcon(normal_icon, 0);
286           Icon overlay = null;
287           if (myVisibility == Visibility.PRIVATE || myVisibility == Visibility.PROTECTED) {
288             overlay = PythonIcons.Python.Nodes.Lock;
289           }
290           else if (myVisibility == Visibility.PREDEFINED) {
291             overlay = PythonIcons.Python.Nodes.Cyan_dot;
292           }
293           else if (myVisibility == Visibility.INVISIBLE) {
294             overlay = PythonIcons.Python.Nodes.Red_inv_triangle;
295           }
296           if (overlay != null) {
297             icon.setIcon(overlay, 1);
298           }
299           return icon;
300         }
301       }
302     };
303   }
304 }