| /* |
| * 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.intellij.codeInsight.hint.actions; |
| |
| import com.intellij.codeInsight.CodeInsightBundle; |
| import com.intellij.codeInsight.TargetElementUtilBase; |
| import com.intellij.codeInsight.documentation.DocumentationManager; |
| import com.intellij.codeInsight.hint.ImplementationViewComponent; |
| import com.intellij.codeInsight.lookup.LookupManager; |
| import com.intellij.codeInsight.navigation.BackgroundUpdaterTask; |
| import com.intellij.codeInsight.navigation.ImplementationSearcher; |
| import com.intellij.featureStatistics.FeatureUsageTracker; |
| import com.intellij.ide.DataManager; |
| import com.intellij.openapi.actionSystem.*; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.fileEditor.FileEditor; |
| import com.intellij.openapi.fileEditor.FileEditorManager; |
| import com.intellij.openapi.fileEditor.TextEditor; |
| import com.intellij.openapi.progress.ProgressIndicator; |
| import com.intellij.openapi.progress.ProgressManager; |
| import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.popup.JBPopup; |
| import com.intellij.openapi.ui.popup.JBPopupFactory; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.pom.PomTargetPsiElement; |
| import com.intellij.psi.*; |
| import com.intellij.psi.presentation.java.SymbolPresentationUtil; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.PsiUtilCore; |
| import com.intellij.reference.SoftReference; |
| import com.intellij.ui.popup.AbstractPopup; |
| import com.intellij.ui.popup.PopupPositionManager; |
| import com.intellij.ui.popup.PopupUpdateProcessor; |
| import com.intellij.usages.UsageView; |
| import com.intellij.util.Processor; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.annotations.TestOnly; |
| |
| import java.lang.ref.WeakReference; |
| import java.util.*; |
| |
| public class ShowImplementationsAction extends AnAction implements PopupAction { |
| @NonNls public static final String CODEASSISTS_QUICKDEFINITION_LOOKUP_FEATURE = "codeassists.quickdefinition.lookup"; |
| @NonNls public static final String CODEASSISTS_QUICKDEFINITION_FEATURE = "codeassists.quickdefinition"; |
| |
| private static final Logger LOG = Logger.getInstance("#" + ShowImplementationsAction.class.getName()); |
| |
| private WeakReference<JBPopup> myPopupRef; |
| private WeakReference<BackgroundUpdaterTask> myTaskRef; |
| |
| public ShowImplementationsAction() { |
| setEnabledInModalContext(true); |
| setInjectedContext(true); |
| } |
| |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| performForContext(e.getDataContext(), true); |
| } |
| |
| @TestOnly |
| public void performForContext(DataContext dataContext) { |
| performForContext(dataContext, true); |
| } |
| |
| @Override |
| public void update(final AnActionEvent e) { |
| Project project = e.getData(CommonDataKeys.PROJECT); |
| if (project == null) { |
| e.getPresentation().setEnabled(false); |
| return; |
| } |
| |
| DataContext dataContext = e.getDataContext(); |
| Editor editor = getEditor(dataContext); |
| |
| PsiFile file = CommonDataKeys.PSI_FILE.getData(dataContext); |
| PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext); |
| element = getElement(project, file, editor, element); |
| |
| PsiFile containingFile = element != null ? element.getContainingFile() : file; |
| boolean enabled = !(containingFile == null || !containingFile.getViewProvider().isPhysical()); |
| e.getPresentation().setEnabled(enabled); |
| } |
| |
| |
| protected Editor getEditor(DataContext dataContext) { |
| Editor editor = CommonDataKeys.EDITOR.getData(dataContext); |
| |
| if (editor == null) { |
| final PsiFile file = CommonDataKeys.PSI_FILE.getData(dataContext); |
| if (file != null) { |
| final FileEditor fileEditor = FileEditorManager.getInstance(file.getProject()).getSelectedEditor(file.getVirtualFile()); |
| if (fileEditor instanceof TextEditor) { |
| editor = ((TextEditor)fileEditor).getEditor(); |
| } |
| } |
| } |
| return editor; |
| } |
| |
| public void performForContext(DataContext dataContext, boolean invokedByShortcut) { |
| final Project project = CommonDataKeys.PROJECT.getData(dataContext); |
| if (project == null) return; |
| PsiDocumentManager.getInstance(project).commitAllDocuments(); |
| |
| PsiFile file = CommonDataKeys.PSI_FILE.getData(dataContext); |
| Editor editor = getEditor(dataContext); |
| |
| PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext); |
| boolean isInvokedFromEditor = CommonDataKeys.EDITOR.getData(dataContext) != null; |
| element = getElement(project, file, editor, element); |
| |
| if (element == null && file == null) return; |
| PsiFile containingFile = element != null ? element.getContainingFile() : file; |
| if (containingFile == null || !containingFile.getViewProvider().isPhysical()) return; |
| |
| |
| PsiReference ref = null; |
| if (editor != null) { |
| ref = TargetElementUtilBase.findReference(editor, editor.getCaretModel().getOffset()); |
| if (element == null && ref != null) { |
| element = TargetElementUtilBase.getInstance().adjustReference(ref); |
| } |
| } |
| |
| String text = ""; |
| PsiElement[] impls = new PsiElement[0]; |
| if (element != null) { |
| //if (element instanceof PsiPackage) return; |
| |
| impls = getSelfAndImplementations(editor, element, createImplementationsSearcher()); |
| text = SymbolPresentationUtil.getSymbolPresentableText(element); |
| } |
| |
| if (impls.length == 0 && ref instanceof PsiPolyVariantReference) { |
| final PsiPolyVariantReference polyReference = (PsiPolyVariantReference)ref; |
| text = polyReference.getRangeInElement().substring(polyReference.getElement().getText()); |
| final ResolveResult[] results = polyReference.multiResolve(false); |
| final List<PsiElement> implsList = new ArrayList<PsiElement>(results.length); |
| |
| for (ResolveResult result : results) { |
| final PsiElement resolvedElement = result.getElement(); |
| |
| if (resolvedElement != null && resolvedElement.isPhysical()) { |
| implsList.add(resolvedElement); |
| } |
| } |
| |
| if (!implsList.isEmpty()) { |
| implsList.toArray( impls = new PsiElement[implsList.size()] ); |
| } |
| } |
| |
| |
| showImplementations(impls, project, text, editor, file, element, isInvokedFromEditor, invokedByShortcut); |
| } |
| |
| protected static PsiElement getElement(Project project, PsiFile file, Editor editor, PsiElement element) { |
| if (element == null && editor != null) { |
| element = TargetElementUtilBase.findTargetElement(editor, TargetElementUtilBase.getInstance().getAllAccepted()); |
| final PsiElement adjustedElement = |
| TargetElementUtilBase.getInstance().adjustElement(editor, TargetElementUtilBase.getInstance().getAllAccepted(), element, null); |
| if (adjustedElement != null) { |
| element = adjustedElement; |
| } |
| else if (file != null) { |
| element = DocumentationManager.getInstance(project).getElementFromLookup(editor, file); |
| } |
| } |
| return element; |
| } |
| |
| protected static ImplementationSearcher createImplementationsSearcher() { |
| if (ApplicationManager.getApplication().isUnitTestMode()) { |
| return new ImplementationSearcher() { |
| @Override |
| protected PsiElement[] filterElements(PsiElement element, PsiElement[] targetElements, int offset) { |
| return ShowImplementationsAction.filterElements(targetElements); |
| } |
| }; |
| } |
| else { |
| return new ImplementationSearcher.FirstImplementationsSearcher() { |
| @Override |
| protected PsiElement[] filterElements(PsiElement element, PsiElement[] targetElements, final int offset) { |
| return ShowImplementationsAction.filterElements(targetElements); |
| } |
| }; |
| } |
| } |
| |
| protected void updateElementImplementations(final PsiElement element, final Editor editor, final Project project, final PsiFile file) { |
| PsiElement[] impls = null; |
| String text = ""; |
| if (element != null) { |
| // if (element instanceof PsiPackage) return; |
| PsiFile containingFile = element.getContainingFile(); |
| if (containingFile == null || !containingFile.getViewProvider().isPhysical()) return; |
| |
| impls = getSelfAndImplementations(editor, element, createImplementationsSearcher()); |
| text = SymbolPresentationUtil.getSymbolPresentableText(element); |
| } |
| |
| showImplementations(impls, project, text, editor, file, element, false, false); |
| } |
| |
| protected void showImplementations(final PsiElement[] impls, final Project project, final String text, final Editor editor, final PsiFile file, |
| final PsiElement element, |
| boolean invokedFromEditor, boolean invokedByShortcut) { |
| if (impls == null || impls.length == 0) return; |
| |
| FeatureUsageTracker.getInstance().triggerFeatureUsed(CODEASSISTS_QUICKDEFINITION_FEATURE); |
| if (LookupManager.getInstance(project).getActiveLookup() != null) { |
| FeatureUsageTracker.getInstance().triggerFeatureUsed(CODEASSISTS_QUICKDEFINITION_LOOKUP_FEATURE); |
| } |
| |
| int index = 0; |
| if (invokedFromEditor && file != null && impls.length > 1) { |
| final VirtualFile virtualFile = file.getVirtualFile(); |
| final PsiFile containingFile = impls[0].getContainingFile(); |
| if (virtualFile != null && containingFile != null && virtualFile.equals(containingFile.getVirtualFile())) { |
| final PsiFile secondContainingFile = impls[1].getContainingFile(); |
| if (secondContainingFile != containingFile) { |
| index = 1; |
| } |
| } |
| } |
| |
| final Ref<UsageView> usageView = new Ref<UsageView>(); |
| final String title = CodeInsightBundle.message("implementation.view.title", text); |
| JBPopup popup = SoftReference.dereference(myPopupRef); |
| if (popup != null && popup.isVisible() && popup instanceof AbstractPopup) { |
| final ImplementationViewComponent component = (ImplementationViewComponent) ((AbstractPopup)popup).getComponent(); |
| ((AbstractPopup)popup).setCaption(title); |
| component.update(impls, index); |
| updateInBackground(editor, element, component, title, (AbstractPopup)popup, usageView); |
| if (invokedByShortcut) { |
| ((AbstractPopup)popup).focusPreferredComponent(); |
| } |
| return; |
| } |
| |
| final ImplementationViewComponent component = new ImplementationViewComponent(impls, index); |
| if (component.hasElementsToShow()) { |
| final PopupUpdateProcessor updateProcessor = new PopupUpdateProcessor(project) { |
| @Override |
| public void updatePopup(Object lookupItemObject) { |
| final PsiElement element = lookupItemObject instanceof PsiElement ? (PsiElement)lookupItemObject : DocumentationManager.getInstance(project).getElementFromLookup(editor, file); |
| updateElementImplementations(element, editor, project, file); |
| } |
| }; |
| |
| popup = JBPopupFactory.getInstance().createComponentPopupBuilder(component, component.getPreferredFocusableComponent()) |
| .setProject(project) |
| .addListener(updateProcessor) |
| .addUserData(updateProcessor) |
| .setDimensionServiceKey(project, DocumentationManager.JAVADOC_LOCATION_AND_SIZE, false) |
| .setResizable(true) |
| .setMovable(true) |
| .setRequestFocus(invokedFromEditor && LookupManager.getActiveLookup(editor) == null) |
| .setTitle(title) |
| .setCouldPin(new Processor<JBPopup>() { |
| @Override |
| public boolean process(JBPopup popup) { |
| usageView.set(component.showInUsageView()); |
| myTaskRef = new WeakReference<BackgroundUpdaterTask>(null); |
| popup.cancel(); |
| return false; |
| } |
| }) |
| .setCancelCallback(new Computable<Boolean>() { |
| @Override |
| public Boolean compute() { |
| final BackgroundUpdaterTask task = SoftReference.dereference(myTaskRef); |
| if (task != null) { |
| task.setCanceled(); |
| } |
| return Boolean.TRUE; |
| } |
| }) |
| .createPopup(); |
| |
| updateInBackground(editor, element, component, title, (AbstractPopup)popup, usageView); |
| |
| PopupPositionManager.positionPopupInBestPosition(popup, editor, DataManager.getInstance().getDataContext()); |
| component.setHint(popup, title); |
| |
| myPopupRef = new WeakReference<JBPopup>(popup); |
| } |
| } |
| |
| private void updateInBackground(Editor editor, |
| @Nullable PsiElement element, |
| ImplementationViewComponent component, |
| String title, |
| AbstractPopup popup, Ref<UsageView> usageView) { |
| final BackgroundUpdaterTask updaterTask = SoftReference.dereference(myTaskRef); |
| if (updaterTask != null) { |
| updaterTask.setCanceled(); |
| } |
| |
| if (element == null) return; //already found |
| final ImplementationsUpdaterTask task = new ImplementationsUpdaterTask(element, editor, title, isIncludeAlwaysSelf()); |
| task.init(popup, component, usageView); |
| |
| myTaskRef = new WeakReference<BackgroundUpdaterTask>(task); |
| ProgressManager.getInstance().runProcessWithProgressAsynchronously(task, new BackgroundableProcessIndicator(task) { |
| @Override |
| public boolean isCanceled() { |
| return super.isCanceled() || task.isCanceled(); |
| } |
| }); |
| } |
| |
| protected boolean isIncludeAlwaysSelf() { |
| return true; |
| } |
| |
| private static PsiElement[] getSelfAndImplementations(Editor editor, |
| PsiElement element, |
| final ImplementationSearcher handler) { |
| return getSelfAndImplementations(editor, element, handler, !(element instanceof PomTargetPsiElement)); |
| } |
| |
| protected static PsiElement[] getSelfAndImplementations(Editor editor, |
| PsiElement element, |
| final ImplementationSearcher handler, |
| final boolean includeSelfAlways) { |
| int offset = editor == null ? 0 : editor.getCaretModel().getOffset(); |
| final PsiElement[] handlerImplementations = handler.searchImplementations(element, editor, offset, includeSelfAlways, true); |
| if (handlerImplementations.length > 0) return handlerImplementations; |
| |
| PsiFile psiFile = element.getContainingFile(); |
| if (psiFile == null) { |
| // Magically, it's null for ant property declarations. |
| element = element.getNavigationElement(); |
| psiFile = element.getContainingFile(); |
| if (psiFile == null) return PsiElement.EMPTY_ARRAY; |
| } |
| if (psiFile.getVirtualFile() != null && (element.getTextRange() != null || element instanceof PsiFile)) { |
| return new PsiElement[]{element}; |
| } |
| else { |
| return PsiElement.EMPTY_ARRAY; |
| } |
| } |
| |
| private static PsiElement[] filterElements(final PsiElement[] targetElements) { |
| final Set<PsiElement> unique = new LinkedHashSet<PsiElement>(Arrays.asList(targetElements)); |
| for (final PsiElement elt : targetElements) { |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| @Override |
| public void run() { |
| final PsiFile containingFile = elt.getContainingFile(); |
| LOG.assertTrue(containingFile != null, elt); |
| PsiFile psiFile = containingFile.getOriginalFile(); |
| if (psiFile.getVirtualFile() == null) unique.remove(elt); |
| } |
| }); |
| } |
| // special case for Python (PY-237) |
| // if the definition is the tree parent of the target element, filter out the target element |
| for (int i = 1; i < targetElements.length; i++) { |
| final PsiElement targetElement = targetElements[i]; |
| if (ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() { |
| @Override |
| public Boolean compute() { |
| return PsiTreeUtil.isAncestor(targetElement, targetElements[0], true); |
| } |
| })) { |
| unique.remove(targetElements[0]); |
| break; |
| } |
| } |
| return PsiUtilCore.toPsiElementArray(unique); |
| } |
| |
| private static class ImplementationsUpdaterTask extends BackgroundUpdaterTask<ImplementationViewComponent> { |
| private final String myCaption; |
| private final Editor myEditor; |
| private final PsiElement myElement; |
| private final boolean myIncludeSelf; |
| private PsiElement[] myElements; |
| |
| public ImplementationsUpdaterTask(final PsiElement element, final Editor editor, final String caption, boolean includeSelf) { |
| super(element.getProject(), ImplementationSearcher.SEARCHING_FOR_IMPLEMENTATIONS); |
| myCaption = caption; |
| myEditor = editor; |
| myElement = element; |
| myIncludeSelf = includeSelf; |
| } |
| |
| @Override |
| public String getCaption(int size) { |
| return myCaption; |
| } |
| |
| @Override |
| protected void paintBusy(boolean paintBusy) { |
| //todo notify busy |
| } |
| |
| @Override |
| protected void replaceModel(@NotNull List<PsiElement> data) { |
| final PsiElement[] elements = myComponent.getElements(); |
| final int includeSelfIdx = myElement instanceof PomTargetPsiElement ? 0 : 1; |
| final int startIdx = elements.length - includeSelfIdx; |
| final PsiElement[] result = new PsiElement[data.size() + includeSelfIdx]; |
| System.arraycopy(elements, 0, result, 0, elements.length); |
| System.arraycopy(PsiUtilCore.toPsiElementArray(data), startIdx, result, elements.length, data.size() - startIdx); |
| myComponent.update(result, myComponent.getIndex()); |
| } |
| |
| @Override |
| public void run(@NotNull final ProgressIndicator indicator) { |
| super.run(indicator); |
| final ImplementationSearcher.BackgroundableImplementationSearcher implementationSearcher = |
| new ImplementationSearcher.BackgroundableImplementationSearcher() { |
| @Override |
| protected void processElement(PsiElement element) { |
| if (!updateComponent(element, null)) { |
| indicator.cancel(); |
| } |
| indicator.checkCanceled(); |
| } |
| |
| @Override |
| protected PsiElement[] filterElements(PsiElement element, PsiElement[] targetElements, int offset) { |
| return ShowImplementationsAction.filterElements(targetElements); |
| } |
| }; |
| if (!myIncludeSelf) { |
| myElements = getSelfAndImplementations(myEditor, myElement, implementationSearcher, false); |
| } else { |
| myElements = getSelfAndImplementations(myEditor, myElement, implementationSearcher); |
| } |
| } |
| |
| @Override |
| public int getCurrentSize() { |
| if (myElements != null) return myElements.length; |
| return super.getCurrentSize(); |
| } |
| |
| @Override |
| public void onSuccess() { |
| if (!setCanceled()) { |
| myComponent.update(myElements, myComponent.getIndex()); |
| } |
| super.onSuccess(); |
| } |
| } |
| } |