package com.intellij.codeInsight.daemon.impl; import com.intellij.codeHighlighting.HighlightDisplayLevel; import com.intellij.codeInsight.CodeInsightSettings; import com.intellij.codeInsight.CodeInsightUtil; import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings; import com.intellij.codeInsight.daemon.HighlightDisplayKey; import com.intellij.codeInsight.daemon.impl.actions.AddImportAction; import com.intellij.codeInsight.daemon.impl.quickfix.PostIntentionsQuickFixAction; import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction; import com.intellij.codeInsight.hint.HintManager; import com.intellij.codeInsight.hint.QuestionAction; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInsight.intention.impl.IntentionActionComposite; import com.intellij.codeInsight.intention.impl.IntentionHintComponent; import com.intellij.codeInsight.intention.impl.config.IntentionManagerSettings; import com.intellij.codeInsight.lookup.LookupManager; import com.intellij.codeInsight.template.impl.TemplateManagerImpl; import com.intellij.codeInsight.template.impl.TemplateState; import com.intellij.lang.annotation.HighlightSeverity; import com.intellij.openapi.actionSystem.ActionManager; import com.intellij.openapi.actionSystem.IdeActions; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.keymap.KeymapUtil; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.PsiShortNamesCache; import java.awt.*; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; /** * @fabrique */ public class ShowIntentionsPass extends TextEditorHighlightingPass { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.ShowIntentionsPass"); private final Project myProject; private final Editor myEditor; private final IntentionAction[] myIntentionActions; private final PsiFile myFile; private boolean myIsSecondPass; private int myStartOffset; private int myEndOffset; ShowIntentionsPass(Project project, Editor editor, IntentionAction[] intentionActions, boolean isSecondPass) { super(editor.getDocument()); LOG.assertTrue(ApplicationManager.getApplication().isDispatchThread()); myIsSecondPass = isSecondPass; myProject = project; myEditor = editor; myIntentionActions = intentionActions; Rectangle visibleRect = myEditor.getScrollingModel().getVisibleArea(); LogicalPosition startPosition = myEditor.xyToLogicalPosition(new Point(visibleRect.x, visibleRect.y)); myStartOffset = myEditor.logicalPositionToOffset(startPosition); LogicalPosition endPosition = myEditor.xyToLogicalPosition( new Point(visibleRect.x + visibleRect.width, visibleRect.y + visibleRect.height)); myEndOffset = myEditor.logicalPositionToOffset(new LogicalPosition(endPosition.line + 1, 0)); myFile = PsiDocumentManager.getInstance(myProject).getPsiFile(myEditor.getDocument()); } public void doCollectInformation(ProgressIndicator progress) { } public void doApplyInformationToEditor() { LOG.assertTrue(ApplicationManager.getApplication().isDispatchThread()); if (!myEditor.getContentComponent().hasFocus()) return; HighlightInfo[] visibleHighlights = getVisibleHighlights(myStartOffset, myEndOffset); if (visibleHighlights == null) return; PsiElement[] elements = new PsiElement[visibleHighlights.length]; for (int i = 0; i < visibleHighlights.length; i++) { ProgressManager.getInstance().checkCanceled(); HighlightInfo highlight = visibleHighlights[i]; elements[i] = myFile.findElementAt(highlight.startOffset); } int caretOffset = myEditor.getCaretModel().getOffset(); for (int i = visibleHighlights.length - 1; i >= 0; i--) { HighlightInfo info = visibleHighlights[i]; if (elements[i] == null) continue; if (info.startOffset <= caretOffset) { if (showAddImportHint(info, elements[i])) return; } } for (int i = 0; i < visibleHighlights.length; i++) { HighlightInfo info = visibleHighlights[i]; if (elements[i] == null) continue; if (info.startOffset > caretOffset) { if (showAddImportHint(info, elements[i])) return; } } if (!(myFile instanceof PsiCodeFragment)) { TemplateState state = TemplateManagerImpl.getTemplateState(myEditor); if (state == null || state.isFinished()) { showIntentionActions(); } } } public int getPassId() { return myIsSecondPass ? Pass.POPUP_HINTS2 : Pass.POPUP_HINTS; } private void showIntentionActions() { DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(myProject); if (myIsSecondPass) codeAnalyzer.setShowPostIntentions(true); if (LookupManager.getInstance(myProject).getActiveLookup() != null) return; // do not show intentions if caret is outside visible area LogicalPosition caretPos = myEditor.getCaretModel().getLogicalPosition(); Rectangle visibleArea = myEditor.getScrollingModel().getVisibleArea(); Point xy = myEditor.logicalPositionToXY(caretPos); if (!visibleArea.contains(xy)) return; ArrayList intentionsToShow = new ArrayList(); ArrayList fixesToShow = new ArrayList(); for (int i = 0; i < myIntentionActions.length; i++) { IntentionAction action = myIntentionActions[i]; if (action instanceof IntentionActionComposite) { if (action instanceof QuickFixAction || action instanceof PostIntentionsQuickFixAction && codeAnalyzer.showPostIntentions()) { List availableActions = ((IntentionActionComposite)action).getAvailableActions(myEditor, myFile); int offset = myEditor.getCaretModel().getOffset(); HighlightInfo info = codeAnalyzer.findHighlightByOffset(myEditor.getDocument(), offset, true); if (info == null || info.getSeverity() == HighlightSeverity.ERROR) { fixesToShow.addAll(availableActions); } else { intentionsToShow.addAll(availableActions); } } } else if (action.isAvailable(myProject, myEditor, myFile)) { intentionsToShow.add(action); } } if (!intentionsToShow.isEmpty() || !fixesToShow.isEmpty()) { boolean showBulb = false; for (Iterator iterator = fixesToShow.iterator(); iterator.hasNext();) { IntentionAction action = iterator.next(); if (IntentionManagerSettings.getInstance().isShowLightBulb(action)) { showBulb = true; break; } } if (!showBulb) { for (Iterator iterator = intentionsToShow.iterator(); iterator.hasNext();) { IntentionAction action = iterator.next(); if (IntentionManagerSettings.getInstance().isShowLightBulb(action)) { showBulb = true; break; } } } if (showBulb) { if (myIsSecondPass) { IntentionHintComponent hintComponent = codeAnalyzer.getLastIntentionHint(); if (hintComponent != null) { hintComponent.updateIfNotShowingPopup(fixesToShow, intentionsToShow); } } if (!HintManager.getInstance().hasShownHintsThatWillHideByOtherHint()) { IntentionHintComponent hintComponent = IntentionHintComponent.showIntentionHint(myProject, myEditor, intentionsToShow, fixesToShow, false); if (!myIsSecondPass) { codeAnalyzer.setLastIntentionHint(hintComponent); } } } } } private HighlightInfo[] getVisibleHighlights(int startOffset, int endOffset) { HighlightInfo[] highlights = DaemonCodeAnalyzerImpl.getHighlights(myEditor.getDocument(), myProject); if (highlights == null) return null; ArrayList array = new ArrayList(); for (int i = 0; i < highlights.length; i++) { HighlightInfo info = highlights[i]; if (!canBeHint(info.type)) continue; if (startOffset <= info.startOffset && info.endOffset <= endOffset) { if (myEditor.getFoldingModel().isOffsetCollapsed(info.startOffset)) continue; array.add(info); } } return array.toArray(new HighlightInfo[array.size()]); } private boolean showAddImportHint(HighlightInfo info, PsiElement element) { if (!element.isWritable()) return false; if (!DaemonCodeAnalyzerSettings.getInstance().isImportHintEnabled()) return false; if (!DaemonCodeAnalyzer.getInstance(myProject).isImportHintsEnabled(myFile)) return false; element = element.getParent(); if (!(element instanceof PsiJavaCodeReferenceElement)) return false; // [dsl]todo[cdr]: please review this // if (element.getTextOffset() != info.startOffset || element.getTextRange().getEndOffset() != info.endOffset) return false; if (info.type == HighlightInfoType.WRONG_REF) { return showAddImportHint(myEditor, (PsiJavaCodeReferenceElement)element); } else if (info.type == HighlightInfoType.JAVADOC_WRONG_REF) { if (DaemonCodeAnalyzerSettings.getInstance().getInspectionProfile().getErrorLevel(HighlightDisplayKey.JAVADOC_ERROR) == HighlightDisplayLevel.ERROR) { return showAddImportHint(myEditor, (PsiJavaCodeReferenceElement)element); } } return false; } /* * @fabrique */ public static boolean showAddImportHint(Editor editor, final PsiJavaCodeReferenceElement ref) { if (HintManager.getInstance().hasShownHintsThatWillHideByOtherHint()) return false; PsiManager manager = ref.getManager(); ApplicationManager.getApplication().assertReadAccessAllowed(); PsiShortNamesCache cache = manager.getShortNamesCache(); PsiElement refname = ref.getReferenceNameElement(); if (!(refname instanceof PsiIdentifier)) { return false; } PsiElement refElement = ref.resolve(); if (refElement != null) { return false; } String name = ref.getQualifiedName(); if (manager.getResolveHelper().resolveReferencedClass(name, ref) != null) return false; GlobalSearchScope scope = ref.getResolveScope(); PsiClass[] classes = cache.getClassesByName(name, scope); if (classes.length == 0) return false; try { Pattern pattern = Pattern.compile(DaemonCodeAnalyzerSettings.getInstance().NO_AUTO_IMPORT_PATTERN); Matcher matcher = pattern.matcher(name); if (matcher.matches()) return false; } catch (PatternSyntaxException e) { } List availableClasses = new ArrayList(); boolean isAnnotationReference = ref.getParent() instanceof PsiAnnotation; for (int j = 0; j < classes.length; j++) { PsiClass aClass = classes[j]; if (aClass.getParent() instanceof PsiDeclarationStatement) continue; PsiFile file = aClass.getContainingFile(); if (!(file instanceof PsiJavaFile) || ((PsiJavaFile)file).getPackageName().length() == 0) continue; if (isAnnotationReference && !aClass.isAnnotationType()) continue; if (!aClass.hasModifierProperty(PsiModifier.PUBLIC)) continue; availableClasses.add(aClass); } if (availableClasses.size() == 0) return false; int refTypeArgsLength = ref.getParameterList().getTypeArguments().length; if (availableClasses.size() > 0 && refTypeArgsLength != 0) { List typeArgMatched = new ArrayList(availableClasses); // try to reduce suggestions based on type argument list for (int i = typeArgMatched.size() - 1; i >= 0; i--) { final PsiClass aClass = typeArgMatched.get(i); PsiTypeParameter[] typeParameters = aClass.getTypeParameters(); if (refTypeArgsLength != typeParameters.length) { typeArgMatched.remove(i); } } if (typeArgMatched.size() != 0) { availableClasses = typeArgMatched; } } classes = availableClasses.toArray(new PsiClass[availableClasses.size()]); CodeInsightUtil.sortIdenticalShortNameClasses(classes); String hintText = classes[0].getQualifiedName() + "? "; if (classes.length > 1) { hintText += "(multiple choices...) "; } hintText += KeymapUtil.getFirstKeyboardShortcutText(ActionManager.getInstance().getAction(IdeActions.ACTION_SHOW_INTENTION_ACTIONS)); int offset1 = ref.getTextOffset(); int offset2 = ref.getTextRange().getEndOffset(); QuestionAction action = new AddImportAction(manager.getProject(), ref, classes, editor); if (classes.length == 1 && CodeInsightSettings.getInstance().ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY) { action.execute(); return false; } HintManager hintManager = HintManager.getInstance(); hintManager.showQuestionHint(editor, hintText, offset1, offset2, action); return true; } private static boolean canBeHint(HighlightInfoType type) { return type == HighlightInfoType.WRONG_REF || type == HighlightInfoType.JAVADOC_WRONG_REF; } }