blob: 376c2428961796f1530840b0e0ed0e00fe02faaa [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.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();
}
}
}