get rid of intellij.build.toolbox.litegen parameter and use BuildOptions.TOOLBOX_LITE...
[idea/community.git] / java / java-impl / src / com / intellij / codeInspection / deadCode / UnusedDeclarationPresentation.java
1 // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.codeInspection.deadCode;
3
4 import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
5 import com.intellij.codeInspection.*;
6 import com.intellij.codeInspection.ex.*;
7 import com.intellij.codeInspection.reference.*;
8 import com.intellij.codeInspection.ui.*;
9 import com.intellij.codeInspection.unusedSymbol.UnusedSymbolLocalInspectionBase;
10 import com.intellij.codeInspection.util.RefFilter;
11 import com.intellij.concurrency.ConcurrentCollectionFactory;
12 import com.intellij.icons.AllIcons;
13 import com.intellij.ide.util.PsiNavigationSupport;
14 import com.intellij.lang.annotation.HighlightSeverity;
15 import com.intellij.openapi.actionSystem.ActionManager;
16 import com.intellij.openapi.actionSystem.AnActionEvent;
17 import com.intellij.openapi.application.ApplicationManager;
18 import com.intellij.openapi.application.ReadAction;
19 import com.intellij.openapi.editor.Document;
20 import com.intellij.openapi.project.Project;
21 import com.intellij.openapi.util.AtomicNotNullLazyValue;
22 import com.intellij.openapi.util.SystemInfo;
23 import com.intellij.openapi.util.TextRange;
24 import com.intellij.openapi.util.text.StringUtil;
25 import com.intellij.openapi.vfs.VfsUtil;
26 import com.intellij.openapi.vfs.VirtualFile;
27 import com.intellij.openapi.vfs.VirtualFileManager;
28 import com.intellij.pom.Navigatable;
29 import com.intellij.profile.codeInspection.ui.SingleInspectionProfilePanel;
30 import com.intellij.psi.*;
31 import com.intellij.psi.util.PropertyUtilBase;
32 import com.intellij.refactoring.safeDelete.SafeDeleteHandler;
33 import com.intellij.ui.HyperlinkAdapter;
34 import com.intellij.ui.ScrollPaneFactory;
35 import com.intellij.util.ArrayUtil;
36 import com.intellij.util.VisibilityUtil;
37 import com.intellij.util.containers.ContainerUtil;
38 import com.intellij.util.text.CharArrayUtil;
39 import com.intellij.util.text.DateFormatUtil;
40 import com.intellij.util.ui.JBUI;
41 import com.intellij.util.ui.UIUtil;
42 import org.jdom.Element;
43 import org.jetbrains.annotations.NonNls;
44 import org.jetbrains.annotations.NotNull;
45 import org.jetbrains.annotations.Nullable;
46
47 import javax.swing.*;
48 import javax.swing.event.HyperlinkEvent;
49 import javax.swing.text.AttributeSet;
50 import javax.swing.text.SimpleAttributeSet;
51 import javax.swing.text.html.HTML;
52 import javax.swing.text.html.HTMLDocument;
53 import javax.swing.text.html.HTMLEditorKit;
54 import javax.swing.text.html.StyleSheet;
55 import java.awt.event.InputEvent;
56 import java.awt.event.KeyEvent;
57 import java.awt.event.MouseEvent;
58 import java.net.URL;
59 import java.util.*;
60 import java.util.function.Consumer;
61 import java.util.function.Predicate;
62 import java.util.stream.Collectors;
63
64 public class UnusedDeclarationPresentation extends DefaultInspectionToolPresentation {
65   private final Map<RefEntity, UnusedDeclarationHint> myFixedElements =
66     ConcurrentCollectionFactory.createMap(ContainerUtil.identityStrategy());
67   private final Set<RefEntity> myExcludedElements = ConcurrentCollectionFactory.createConcurrentSet(ContainerUtil.identityStrategy());
68
69   private final WeakUnreferencedFilter myFilter;
70   private DeadHTMLComposer myComposer;
71   private final AtomicNotNullLazyValue<InspectionToolWrapper> myDummyWrapper = new AtomicNotNullLazyValue<InspectionToolWrapper>() {
72     @NotNull
73     @Override
74     protected InspectionToolWrapper compute() {
75       InspectionToolWrapper toolWrapper = new GlobalInspectionToolWrapper(new DummyEntryPointsEP());
76       toolWrapper.initialize(myContext);
77       return toolWrapper;
78     }
79   };
80
81   @NonNls private static final String DELETE = "delete";
82   @NonNls private static final String COMMENT = "comment";
83
84   private enum UnusedDeclarationHint {
85     COMMENT("Commented out"),
86     DELETE("Deleted");
87
88     private final String myDescription;
89
90     UnusedDeclarationHint(String description) {
91       myDescription = description;
92     }
93
94     public String getDescription() {
95       return myDescription;
96     }
97   }
98
99   public UnusedDeclarationPresentation(@NotNull InspectionToolWrapper toolWrapper, @NotNull GlobalInspectionContextImpl context) {
100     super(toolWrapper, context);
101     myQuickFixActions = createQuickFixes(toolWrapper);
102     myFilter = new WeakUnreferencedFilter(getTool(), getContext());
103     ((EntryPointsManagerBase)getEntryPointsManager()).setAddNonJavaEntries(getTool().ADD_NONJAVA_TO_ENTRIES);
104   }
105
106   @NotNull
107   public RefFilter getFilter() {
108     return myFilter;
109   }
110   private static class WeakUnreferencedFilter extends UnreferencedFilter {
111     private WeakUnreferencedFilter(@NotNull UnusedDeclarationInspectionBase tool, @NotNull GlobalInspectionContextImpl context) {
112       super(tool, context);
113     }
114
115     @Override
116     public int getElementProblemCount(@NotNull final RefJavaElement refElement) {
117       final int problemCount = super.getElementProblemCount(refElement);
118       if (problemCount > - 1) return problemCount;
119       if (!((RefElementImpl)refElement).hasSuspiciousCallers() || ((RefJavaElementImpl)refElement).isSuspiciousRecursive()) return 1;
120
121       for (RefElement element : refElement.getInReferences()) {
122         if (refElement instanceof RefFile) return 1;
123         if (((UnusedDeclarationInspectionBase)myTool).isEntryPoint(element)) return 1;
124       }
125
126       return 0;
127     }
128   }
129
130   @NotNull
131   private UnusedDeclarationInspectionBase getTool() {
132     return (UnusedDeclarationInspectionBase)getToolWrapper().getTool();
133   }
134
135
136   @Override
137   @NotNull
138   public DeadHTMLComposer getComposer() {
139     if (myComposer == null) {
140       myComposer = new DeadHTMLComposer(this);
141     }
142     return myComposer;
143   }
144
145   @Override
146   public boolean isExcluded(@NotNull RefEntity entity) {
147     return myExcludedElements.contains(entity);
148   }
149
150
151   @Override
152   public void amnesty(@NotNull RefEntity element) {
153     myExcludedElements.remove(element);
154   }
155
156   @Override
157   public void exclude(@NotNull RefEntity element) {
158     myExcludedElements.add(element);
159   }
160
161   @Override
162   public void exportResults(@NotNull Consumer<? super Element> resultConsumer,
163                             @NotNull RefEntity refEntity,
164                             @NotNull Predicate<? super CommonProblemDescriptor> excludedDescriptions) {
165     if (!(refEntity instanceof RefJavaElement)) return;
166     final RefFilter filter = getFilter();
167     if (!myFixedElements.containsKey(refEntity) && filter.accepts((RefJavaElement)refEntity)) {
168       refEntity = getRefManager().getRefinedElement(refEntity);
169       if (!refEntity.isValid()) return;
170       RefJavaElement refElement = (RefJavaElement)refEntity;
171       if (!compareVisibilities(refElement, getTool().getSharedLocalInspectionTool())) return;
172       if (skipEntryPoints(refElement)) return;
173
174       Element element = refEntity.getRefManager().export(refEntity, -1);
175       if (element == null) return;
176       @NonNls Element problemClassElement = new Element(InspectionsBundle.message("inspection.export.results.problem.element.tag"));
177
178       final HighlightSeverity severity = getSeverity(refElement);
179       final String attributeKey = HighlightInfoType.UNUSED_SYMBOL.getAttributesKey().getExternalName();
180       problemClassElement.setAttribute("severity", severity.myName);
181       problemClassElement.setAttribute("attribute_key", attributeKey);
182
183       problemClassElement.addContent(InspectionsBundle.message("inspection.export.results.dead.code"));
184       element.addContent(problemClassElement);
185
186       @NonNls Element hintsElement = new Element("hints");
187
188       for (UnusedDeclarationHint hint : UnusedDeclarationHint.values()) {
189         @NonNls Element hintElement = new Element("hint");
190         hintElement.setAttribute("value", StringUtil.toLowerCase(hint.toString()));
191         hintsElement.addContent(hintElement);
192       }
193       element.addContent(hintsElement);
194
195
196       Element descriptionElement = new Element(InspectionsBundle.message("inspection.export.results.description.tag"));
197       StringBuffer buf = new StringBuffer();
198       DeadHTMLComposer.appendProblemSynopsis((RefElement)refEntity, buf);
199       descriptionElement.addContent(buf.toString());
200       element.addContent(descriptionElement);
201       resultConsumer.accept(element);
202     }
203     super.exportResults(resultConsumer, refEntity, excludedDescriptions);
204   }
205
206   @NotNull
207   @Override
208   public QuickFixAction[] getQuickFixes(@NotNull RefEntity... refElements) {
209     return Arrays.stream(refElements).anyMatch(element -> element instanceof RefJavaElement && getFilter().accepts((RefJavaElement)element) && !myFixedElements.containsKey(element) && element.isValid())
210            ? myQuickFixActions
211            : QuickFixAction.EMPTY;
212   }
213
214   final QuickFixAction[] myQuickFixActions;
215
216   @NotNull
217   private QuickFixAction[] createQuickFixes(@NotNull InspectionToolWrapper toolWrapper) {
218     return new QuickFixAction[]{new PermanentDeleteAction(toolWrapper), new CommentOutBin(toolWrapper), new MoveToEntries(toolWrapper)};
219   }
220   private static final String DELETE_QUICK_FIX = InspectionsBundle.message("inspection.dead.code.safe.delete.quickfix");
221
222   class PermanentDeleteAction extends QuickFixAction {
223     PermanentDeleteAction(@NotNull InspectionToolWrapper toolWrapper) {
224       super(DELETE_QUICK_FIX, AllIcons.Actions.Cancel, null, toolWrapper);
225       copyShortcutFrom(ActionManager.getInstance().getAction("SafeDelete"));
226     }
227
228     @Override
229     protected boolean applyFix(@NotNull final RefEntity[] refElements) {
230       if (!super.applyFix(refElements)) return false;
231
232       //filter only elements applicable to be deleted (exclude entry points)
233       RefElement[] filteredRefElements = Arrays.stream(refElements)
234         .filter(entry -> entry instanceof RefJavaElement && getFilter().accepts((RefJavaElement)entry))
235         .toArray(RefElement[]::new);
236
237       ApplicationManager.getApplication().invokeLater(() -> {
238         final Project project = getContext().getProject();
239         if (isDisposed() || project.isDisposed()) return;
240         Set<RefEntity> classes = Arrays.stream(filteredRefElements)
241           .filter(refElement -> refElement instanceof RefClass)
242           .collect(Collectors.toSet());
243
244         //filter out elements inside classes to be deleted
245         PsiElement[] elements = Arrays.stream(filteredRefElements).filter(e -> {
246           RefEntity owner = e.getOwner();
247           if (owner != null && classes.contains(owner)) {
248             return false;
249           }
250           return true;
251         }).map(e -> e.getPsiElement())
252           .filter(e -> e != null)
253           .toArray(PsiElement[]::new);
254         SafeDeleteHandler.invoke(project, elements, false,
255                                  () -> {
256                                    removeElements(filteredRefElements, project, myToolWrapper);
257                                    for (RefEntity ref : filteredRefElements) {
258                                      myFixedElements.put(ref, UnusedDeclarationHint.DELETE);
259                                    }
260                                  });
261       });
262
263       return false; //refresh after safe delete dialog is closed
264     }
265   }
266
267   private EntryPointsManager getEntryPointsManager() {
268     return getContext().getExtension(GlobalJavaInspectionContext.CONTEXT).getEntryPointsManager(getContext().getRefManager());
269   }
270
271   class MoveToEntries extends QuickFixAction {
272     MoveToEntries(@NotNull InspectionToolWrapper toolWrapper) {
273       super(InspectionsBundle.message("inspection.dead.code.entry.point.quickfix"), null, null, toolWrapper);
274     }
275
276     @Override
277     public void update(@NotNull AnActionEvent e) {
278       super.update(e);
279       if (e.getPresentation().isEnabledAndVisible()) {
280         final RefEntity[] elements = getInvoker(e).getTree().getSelectedElements();
281         for (RefEntity element : elements) {
282           if (!((RefElement) element).isEntry()) {
283             return;
284           }
285         }
286         e.getPresentation().setEnabled(false);
287       }
288     }
289
290     @Override
291     protected boolean applyFix(@NotNull RefEntity[] refElements) {
292       final EntryPointsManager entryPointsManager = getEntryPointsManager();
293       for (RefEntity refElement : refElements) {
294         if (refElement instanceof RefElement) {
295           entryPointsManager.addEntryPoint((RefElement)refElement, true);
296         }
297       }
298
299       return true;
300     }
301   }
302
303   class CommentOutBin extends QuickFixAction {
304     CommentOutBin(@NotNull InspectionToolWrapper toolWrapper) {
305       super(COMMENT_OUT_QUICK_FIX, null, KeyStroke.getKeyStroke(KeyEvent.VK_SLASH, SystemInfo.isMac ? InputEvent.META_MASK : InputEvent.CTRL_MASK),
306             toolWrapper);
307     }
308
309     @Override
310     protected boolean applyFix(@NotNull RefEntity[] refElements) {
311       if (!super.applyFix(refElements)) return false;
312       List<RefElement> deletedRefs = new ArrayList<>(1);
313       final RefFilter filter = getFilter();
314       for (RefEntity refElement : refElements) {
315         PsiElement psiElement = refElement instanceof RefElement ? ((RefElement)refElement).getPsiElement() : null;
316         if (psiElement == null) continue;
317         if (filter.getElementProblemCount((RefJavaElement)refElement) == 0) continue;
318
319         final RefEntity owner = refElement.getOwner();
320         if (!(owner instanceof RefJavaElement) || filter.getElementProblemCount((RefJavaElement)owner) == 0 || !(ArrayUtil.find(refElements, owner) > -1)) {
321           commentOutDead(psiElement);
322         }
323
324         refElement.getRefManager().removeRefElement((RefElement)refElement, deletedRefs);
325       }
326
327       EntryPointsManager entryPointsManager = getEntryPointsManager();
328       for (RefElement refElement : deletedRefs) {
329         entryPointsManager.removeEntryPoint(refElement);
330       }
331
332       for (RefElement ref : deletedRefs) {
333         myFixedElements.put(ref, UnusedDeclarationHint.COMMENT);
334       }
335       return true;
336     }
337   }
338
339   private static final String COMMENT_OUT_QUICK_FIX = InspectionsBundle.message("inspection.dead.code.comment.quickfix");
340   private static class CommentOutFix implements QuickFix {
341     private final RefElement myElement;
342
343     private CommentOutFix(RefElement element) {
344       myElement = element;
345     }
346
347     @Override
348     @NotNull
349     public String getFamilyName() {
350       return COMMENT_OUT_QUICK_FIX;
351     }
352
353     @Override
354     public void applyFix(@NotNull Project project, @NotNull CommonProblemDescriptor descriptor) {
355       if (myElement != null) {
356         PsiElement element = myElement.getPsiElement();
357         if (element != null) {
358           commentOutDead(element);
359         }
360       }
361     }
362
363     @Override
364     public boolean startInWriteAction() {
365       return true;
366     }
367   }
368   private static void commentOutDead(PsiElement psiElement) {
369     PsiFile psiFile = psiElement.getContainingFile();
370
371     if (psiFile != null) {
372       Document doc = PsiDocumentManager.getInstance(psiElement.getProject()).getDocument(psiFile);
373       if (doc != null) {
374         TextRange textRange = psiElement.getTextRange();
375         String date = DateFormatUtil.formatDateTime(new Date());
376
377         int startOffset = textRange.getStartOffset();
378         CharSequence chars = doc.getCharsSequence();
379         while (CharArrayUtil.regionMatches(chars, startOffset, InspectionsBundle.message("inspection.dead.code.comment"))) {
380           int line = doc.getLineNumber(startOffset) + 1;
381           if (line < doc.getLineCount()) {
382             startOffset = doc.getLineStartOffset(line);
383             startOffset = CharArrayUtil.shiftForward(chars, startOffset, " \t");
384           }
385         }
386
387         int endOffset = textRange.getEndOffset();
388
389         int line1 = doc.getLineNumber(startOffset);
390         int line2 = doc.getLineNumber(endOffset - 1);
391
392         if (line1 == line2) {
393           doc.insertString(startOffset, InspectionsBundle.message("inspection.dead.code.date.comment", date));
394         }
395         else {
396           for (int i = line1; i <= line2; i++) {
397             doc.insertString(doc.getLineStartOffset(i), "//");
398           }
399
400           doc.insertString(doc.getLineStartOffset(Math.min(line2 + 1, doc.getLineCount() - 1)),
401                            InspectionsBundle.message("inspection.dead.code.stop.comment", date));
402           doc.insertString(doc.getLineStartOffset(line1), InspectionsBundle.message("inspection.dead.code.start.comment", date));
403         }
404       }
405     }
406   }
407
408   @Override
409   public void patchToolNode(@NotNull InspectionTreeNode node,
410                             @NotNull InspectionRVContentProvider provider,
411                             boolean showStructure,
412                             boolean groupByStructure) {
413     InspectionTreeModel model = myContext.getView().getTree().getInspectionTreeModel();
414     EntryPointsNode epNode = model.createCustomNode(myDummyWrapper.getValue(), () -> new EntryPointsNode(myDummyWrapper.getValue(), myContext, node), node);
415     InspectionToolPresentation presentation = myContext.getPresentation(myDummyWrapper.getValue());
416     presentation.updateContent();
417     provider.appendToolNodeContent(myContext, myDummyWrapper.getValue(), epNode, showStructure, groupByStructure);
418   }
419
420   @NotNull
421   @Override
422   public RefElementNode createRefNode(@Nullable RefEntity entity, @NotNull InspectionTreeModel model, @NotNull InspectionTreeNode parent) {
423     return new RefElementNode(entity, this, parent) {
424       @Nullable
425       @Override
426       public String getTailText() {
427         final UnusedDeclarationHint hint = myFixedElements.get(getElement());
428         if (hint != null) {
429           return hint.getDescription();
430         }
431         return super.getTailText();
432       }
433
434       @Override
435       public boolean isQuickFixAppliedFromView() {
436         return myFixedElements.containsKey(getElement());
437       }
438     };
439   }
440
441   @Override
442   public boolean isProblemResolved(@Nullable RefEntity entity) {
443     return myFixedElements.containsKey(entity);
444   }
445
446   @Override
447   public synchronized void updateContent() {
448     getTool().checkForReachableRefs(getContext());
449     myContents.clear();
450     final UnusedSymbolLocalInspectionBase localInspectionTool = getTool().getSharedLocalInspectionTool();
451     getContext().getRefManager().iterate(new RefJavaVisitor() {
452       @Override public void visitElement(@NotNull RefEntity refEntity) {
453         if (!(refEntity instanceof RefJavaElement)) return;//dead code doesn't work with refModule | refPackage
454         RefJavaElement refElement = (RefJavaElement)refEntity;
455         if (!compareVisibilities(refElement, localInspectionTool)) return;
456         if (!(getContext().getUIOptions().FILTER_RESOLVED_ITEMS &&
457               (myFixedElements.containsKey(refElement) ||
458               isExcluded(refEntity) ||
459               isSuppressed(refElement))) && refElement.isValid() && getFilter().accepts(refElement)) {
460           if (skipEntryPoints(refElement)) return;
461           registerContentEntry(refEntity, RefJavaUtil.getInstance().getPackageName(refEntity));
462         }
463       }
464     });
465     updateProblemElements();
466   }
467
468   protected boolean skipEntryPoints(RefJavaElement refElement) {
469     return getTool().isEntryPoint(refElement);
470   }
471
472   @PsiModifier.ModifierConstant
473   private static String getAcceptedVisibility(UnusedSymbolLocalInspectionBase tool, RefJavaElement element) {
474     if (element instanceof RefImplicitConstructor) {
475       element = ((RefImplicitConstructor)element).getOwnerClass();
476     }
477     if (element instanceof RefClass) {
478       return element.getOwner() instanceof RefClass ? tool.getInnerClassVisibility() : tool.getClassVisibility();
479     }
480     if (element instanceof RefField) {
481       return tool.getFieldVisibility();
482     }
483     if (element instanceof RefMethod) {
484       final String methodVisibility = tool.getMethodVisibility();
485       if (methodVisibility != null &&
486           //todo store in the graph
487           tool.isIgnoreAccessors()) {
488         final PsiElement psi = element.getPsiElement();
489         if (psi instanceof PsiMethod && PropertyUtilBase.isSimplePropertyAccessor((PsiMethod)psi)) {
490           return null;
491         }
492       }
493       return methodVisibility;
494     }
495     if (element instanceof RefParameter) {
496       return tool.getParameterVisibility();
497     }
498     return PsiModifier.PUBLIC;
499   }
500
501   protected static boolean compareVisibilities(RefJavaElement listOwner,
502                                                UnusedSymbolLocalInspectionBase localInspectionTool) {
503     return compareVisibilities(listOwner, getAcceptedVisibility(localInspectionTool, listOwner));
504   }
505
506   protected static boolean compareVisibilities(RefJavaElement listOwner, final String acceptedVisibility) {
507     if (acceptedVisibility != null) {
508       while (listOwner != null) {
509         if (VisibilityUtil.compare(listOwner.getAccessModifier(), acceptedVisibility) >= 0) {
510           return true;
511         }
512         final RefEntity parent = listOwner.getOwner();
513         if (parent instanceof RefJavaElement) {
514           listOwner = (RefJavaElement)parent;
515         }
516         else {
517           break;
518         }
519       }
520     }
521     return false;
522   }
523
524   @Override
525   public void ignoreElement(@NotNull RefEntity refEntity) {
526     RefEntity owner = refEntity;
527     if (refEntity instanceof RefParameter) {
528       owner = refEntity.getOwner();
529     }
530
531     final CommonProblemDescriptor[] descriptors = getProblemElements().get(owner);
532     if (descriptors != null) {
533       final PsiElement psiElement = ReadAction.compute(() -> ((RefElement)refEntity).getPsiElement());
534       List<CommonProblemDescriptor> foreignDescriptors = new ArrayList<>();
535       for (CommonProblemDescriptor descriptor : descriptors) {
536         if (descriptor instanceof ProblemDescriptor) {
537           PsiElement problemElement = ReadAction.compute(() -> {
538             PsiElement element = ((ProblemDescriptor)descriptor).getPsiElement();
539             if (element instanceof PsiIdentifier) element = element.getParent();
540             return element;
541           });
542           if (problemElement == psiElement) continue;
543         }
544         foreignDescriptors.add(descriptor);
545       }
546       if (foreignDescriptors.size() == descriptors.length) return;
547     }
548     super.ignoreElement(owner);
549   }
550
551   @Override
552   public void cleanup() {
553     super.cleanup();
554     myFixedElements.clear();
555   }
556
557   @Override
558   @Nullable
559   public QuickFix findQuickFixes(@NotNull final CommonProblemDescriptor descriptor,
560                                  RefEntity entity,
561                                  String hint) {
562     if (entity instanceof RefElement) {
563       if (DELETE.equals(hint)) {
564         return new PermanentDeleteFix((RefElement)entity);
565       }
566       if (COMMENT.equals(hint)) {
567         return new CommentOutFix((RefElement)entity);
568       }
569       if (entity instanceof RefParameter) {
570         return super.findQuickFixes(descriptor, entity, hint);
571       }
572     }
573     return null;
574   }
575
576   private static class PermanentDeleteFix implements QuickFix {
577     private final RefElement myElement;
578
579     private PermanentDeleteFix(@Nullable RefElement element) {
580       myElement = element;
581     }
582
583     @Override
584     @NotNull
585     public String getFamilyName() {
586       return DELETE_QUICK_FIX;
587     }
588
589
590     @Override
591     public void applyFix(@NotNull Project project, @NotNull CommonProblemDescriptor descriptor) {
592       if (myElement != null && myElement.isValid()) {
593         SafeDeleteHandler.invoke(project, new PsiElement[]{myElement.getPsiElement()}, false);
594       }
595     }
596
597     @Override
598     public boolean startInWriteAction() {
599       return false;
600     }
601   }
602
603   @NotNull
604   @Override
605   public JComponent getCustomPreviewPanel(@NotNull RefEntity entity) {
606     final Project project = entity.getRefManager().getProject();
607     JEditorPane htmlView = new JEditorPane() {
608       @Override
609       public String getToolTipText(MouseEvent evt) {
610         int pos = viewToModel(evt.getPoint());
611         if (pos >= 0) {
612           HTMLDocument hdoc = (HTMLDocument) getDocument();
613           javax.swing.text.Element e = hdoc.getCharacterElement(pos);
614           AttributeSet a = e.getAttributes();
615
616           SimpleAttributeSet value = (SimpleAttributeSet) a.getAttribute(HTML.Tag.A);
617           if (value != null) {
618             String objectPackage = (String) value.getAttribute("qualifiedname");
619             if (objectPackage != null) {
620               return objectPackage;
621             }
622           }
623         }
624         return null;
625       }
626     };
627     htmlView.setContentType(UIUtil.HTML_MIME);
628     htmlView.setEditable(false);
629     htmlView.setOpaque(false);
630     htmlView.setBackground(UIUtil.getLabelBackground());
631     htmlView.addHyperlinkListener(new HyperlinkAdapter() {
632       @Override
633       protected void hyperlinkActivated(HyperlinkEvent e) {
634         URL url = e.getURL();
635         if (url == null) {
636           return;
637         }
638         @NonNls String ref = url.getRef();
639
640         int offset = Integer.parseInt(ref);
641         String fileURL = url.toExternalForm();
642         fileURL = fileURL.substring(0, fileURL.indexOf('#'));
643         VirtualFile vFile = VirtualFileManager.getInstance().findFileByUrl(fileURL);
644         if (vFile == null) {
645           vFile = VfsUtil.findFileByURL(url);
646         }
647         if (vFile != null) {
648           Navigatable descriptor = PsiNavigationSupport.getInstance().createNavigatable(project, vFile, offset);
649           descriptor.navigate(true);
650         }
651       }
652     });
653     final StyleSheet css = ((HTMLEditorKit)htmlView.getEditorKit()).getStyleSheet();
654     css.addRule("p.problem-description-group {text-indent: " + JBUI.scale(9) + "px;font-weight:bold;}");
655     css.addRule("div.problem-description {margin-left: " + JBUI.scale(9) + "px;}");
656     css.addRule("ul {margin-left:" + JBUI.scale(10) + "px;text-indent: 0}");
657     css.addRule("code {font-family:" + UIUtil.getLabelFont().getFamily()  +  "}");
658     final StringBuffer buf = new StringBuffer();
659     getComposer().compose(buf, entity, false);
660     final String text = buf.toString();
661     SingleInspectionProfilePanel.readHTML(htmlView, SingleInspectionProfilePanel.toHTML(htmlView, text, false));
662     return ScrollPaneFactory.createScrollPane(htmlView, true);
663   }
664
665   @Override
666   public boolean showProblemCount() {
667     return false;
668   }
669 }