blob: 6dcc22637906ffc680d5f7c9fc53fdf89b914949 [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.
*/
/*
* User: anna
* Date: 30-Jan-2008
*/
package com.intellij.codeInsight;
import com.intellij.codeInsight.completion.CompletionUtil;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.codeInsight.lookup.Lookup;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupManager;
import com.intellij.ide.util.EditSourceUtil;
import com.intellij.lang.Language;
import com.intellij.lang.LanguageExtension;
import com.intellij.navigation.NavigationItem;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.actions.EditorActionUtil;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.pom.Navigatable;
import com.intellij.pom.PomDeclarationSearcher;
import com.intellij.pom.PomTarget;
import com.intellij.pom.PsiDeclaredTarget;
import com.intellij.pom.references.PomService;
import com.intellij.psi.*;
import com.intellij.psi.search.PsiSearchHelper;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.Consumer;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class TargetElementUtilBase {
public static final int REFERENCED_ELEMENT_ACCEPTED = 0x01;
public static final int ELEMENT_NAME_ACCEPTED = 0x02;
public static final int LOOKUP_ITEM_ACCEPTED = 0x08;
public static TargetElementUtilBase getInstance() {
return ServiceManager.getService(TargetElementUtilBase.class);
}
public int getAllAccepted() {
return REFERENCED_ELEMENT_ACCEPTED | ELEMENT_NAME_ACCEPTED | LOOKUP_ITEM_ACCEPTED;
}
/**
* Accepts THIS or SUPER but not NEW_AS_CONSTRUCTOR.
*/
public int getDefinitionSearchFlags() {
return getAllAccepted();
}
/**
* Accepts NEW_AS_CONSTRUCTOR but not THIS or SUPER.
*/
public int getReferenceSearchFlags() {
return getAllAccepted();
}
@Nullable
public static PsiReference findReference(Editor editor) {
PsiReference result = findReference(editor, editor.getCaretModel().getOffset());
if (result == null) {
final Integer offset = editor.getUserData(EditorActionUtil.EXPECTED_CARET_OFFSET);
if (offset != null) {
result = findReference(editor, offset);
}
}
return result;
}
@Nullable
public static PsiReference findReference(@NotNull Editor editor, int offset) {
Project project = editor.getProject();
if (project == null) return null;
Document document = editor.getDocument();
PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(document);
if (file == null) return null;
if (ApplicationManager.getApplication().isDispatchThread()) {
PsiDocumentManager.getInstance(project).commitAllDocuments();
}
offset = adjustOffset(file, document, offset);
if (file instanceof PsiCompiledFile) {
return ((PsiCompiledFile) file).getDecompiledPsiFile().findReferenceAt(offset);
}
return file.findReferenceAt(offset);
}
/**
* @deprecated adjust offset with PsiElement should be used instead to provide correct checking for identifier part
*/
public static int adjustOffset(Document document, final int offset) {
return adjustOffset(null, document, offset);
}
public static int adjustOffset(@Nullable PsiFile file, Document document, final int offset) {
CharSequence text = document.getCharsSequence();
int correctedOffset = offset;
int textLength = document.getTextLength();
if (offset >= textLength) {
correctedOffset = textLength - 1;
}
else if (!isIdentifierPart(file, text, offset)) {
correctedOffset--;
}
if (correctedOffset < 0 || !isIdentifierPart(file, text, correctedOffset)) return offset;
return correctedOffset;
}
private static boolean isIdentifierPart(@Nullable PsiFile file, CharSequence text, int offset) {
if (file != null) {
for (TargetElementEvaluator evaluator : getInstance().targetElementEvaluator.allForLanguage(file.getLanguage())) {
if (evaluator instanceof TargetElementEvaluatorEx && ((TargetElementEvaluatorEx)evaluator).isIdentifierPart(file, text, offset)) {
return true;
}
}
}
return Character.isJavaIdentifierPart(text.charAt(offset));
}
@Nullable
public static PsiElement findTargetElement(Editor editor, int flags) {
ApplicationManager.getApplication().assertIsDispatchThread();
final PsiElement result = getInstance().findTargetElement(editor, flags, editor.getCaretModel().getOffset());
if (result != null) {
return result;
}
final Integer offset = editor.getUserData(EditorActionUtil.EXPECTED_CARET_OFFSET);
if (offset != null) {
return getInstance().findTargetElement(editor, flags, offset);
}
return result;
}
public static boolean inVirtualSpace(@NotNull Editor editor, int offset) {
if (offset == editor.getCaretModel().getOffset()) {
return EditorUtil.inVirtualSpace(editor, editor.getCaretModel().getLogicalPosition());
}
return false;
}
@Nullable
public PsiElement findTargetElement(@NotNull Editor editor, int flags, int offset) {
Project project = editor.getProject();
if (project == null) return null;
if ((flags & LOOKUP_ITEM_ACCEPTED) != 0) {
PsiElement element = getTargetElementFromLookup(project);
if (element != null) {
return element;
}
}
Document document = editor.getDocument();
if (ApplicationManager.getApplication().isDispatchThread()) {
PsiDocumentManager.getInstance(project).commitAllDocuments();
}
PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(document);
if (file == null) return null;
offset = adjustOffset(file, document, offset);
if (file instanceof PsiCompiledFile) {
file = ((PsiCompiledFile) file).getDecompiledPsiFile();
}
PsiElement element = file.findElementAt(offset);
if ((flags & REFERENCED_ELEMENT_ACCEPTED) != 0) {
final PsiElement referenceOrReferencedElement = getReferenceOrReferencedElement(file, editor, flags, offset);
//if (referenceOrReferencedElement == null) {
// return getReferenceOrReferencedElement(file, editor, flags, offset);
//}
if (isAcceptableReferencedElement(element, referenceOrReferencedElement)) {
return referenceOrReferencedElement;
}
}
if (element == null) return null;
if ((flags & ELEMENT_NAME_ACCEPTED) != 0) {
if (element instanceof PsiNamedElement) return element;
return getNamedElement(element, offset - element.getTextRange().getStartOffset());
}
return null;
}
@Nullable
private static PsiElement getTargetElementFromLookup(Project project) {
Lookup activeLookup = LookupManager.getInstance(project).getActiveLookup();
if (activeLookup != null) {
LookupElement item = activeLookup.getCurrentItem();
if (item != null && item.isValid()) {
final PsiElement psi = CompletionUtil.getTargetElement(item);
if (psi != null && psi.isValid()) {
return psi;
}
}
}
return null;
}
protected boolean isAcceptableReferencedElement(final PsiElement element, final PsiElement referenceOrReferencedElement) {
return referenceOrReferencedElement != null &&
referenceOrReferencedElement.isValid();
}
@Nullable
public PsiElement adjustElement(final Editor editor, final int flags, final PsiElement element, final PsiElement contextElement) {
return element;
}
@Nullable
public PsiElement adjustReference(@NotNull PsiReference ref){
return null;
}
@Nullable
public PsiElement getNamedElement(@Nullable final PsiElement element, final int offsetInElement) {
if (element == null) return null;
final List<PomTarget> targets = ContainerUtil.newArrayList();
final Consumer<PomTarget> consumer = new Consumer<PomTarget>() {
@Override
public void consume(PomTarget target) {
if (target instanceof PsiDeclaredTarget) {
final PsiDeclaredTarget declaredTarget = (PsiDeclaredTarget)target;
final PsiElement navigationElement = declaredTarget.getNavigationElement();
final TextRange range = declaredTarget.getNameIdentifierRange();
if (range != null && !range.shiftRight(navigationElement.getTextRange().getStartOffset())
.contains(element.getTextRange().getStartOffset() + offsetInElement)) {
return;
}
}
targets.add(target);
}
};
PsiElement parent = element;
int offset = offsetInElement;
while (parent != null) {
for (PomDeclarationSearcher searcher : PomDeclarationSearcher.EP_NAME.getExtensions()) {
searcher.findDeclarationsAt(parent, offset, consumer);
if (!targets.isEmpty()) {
final PomTarget target = targets.get(0);
return target == null ? null : PomService.convertToPsi(element.getProject(), target);
}
}
offset += parent.getStartOffsetInParent();
parent = parent.getParent();
}
return getNamedElement(element);
}
@Nullable
protected PsiElement getNamedElement(@Nullable final PsiElement element) {
PsiElement parent;
if ((parent = PsiTreeUtil.getParentOfType(element, PsiNamedElement.class, false)) != null) {
// A bit hacky depends on navigation offset correctly overridden
assert element != null : "notnull parent?";
if (parent.getTextOffset() == element.getTextRange().getStartOffset()) {
return parent;
}
}
return null;
}
@Nullable
protected PsiElement getReferenceOrReferencedElement(PsiFile file, Editor editor, int flags, int offset) {
PsiReference ref = findReference(editor, offset);
if (ref == null) return null;
final Language language = ref.getElement().getLanguage();
final List<TargetElementEvaluator> evaluators = targetElementEvaluator.allForLanguage(language);
for (TargetElementEvaluator evaluator : evaluators) {
final PsiElement element = evaluator.getElementByReference(ref, flags);
if (element != null) return element;
}
PsiManager manager = file.getManager();
PsiElement refElement = ref.resolve();
if (refElement == null) {
if (ApplicationManager.getApplication().isDispatchThread()) {
DaemonCodeAnalyzer.getInstance(manager.getProject()).updateVisibleHighlighters(editor);
}
return null;
}
else {
return refElement;
}
}
public Collection<PsiElement> getTargetCandidates(PsiReference reference) {
if (reference instanceof PsiPolyVariantReference) {
final ResolveResult[] results = ((PsiPolyVariantReference)reference).multiResolve(false);
final ArrayList<PsiElement> navigatableResults = new ArrayList<PsiElement>(results.length);
for(ResolveResult r:results) {
PsiElement element = r.getElement();
if (EditSourceUtil.canNavigate(element) || element instanceof Navigatable && ((Navigatable)element).canNavigateToSource()) {
navigatableResults.add(element);
}
}
return navigatableResults;
}
PsiElement resolved = reference.resolve();
if (resolved instanceof NavigationItem) {
return Collections.singleton(resolved);
}
return Collections.emptyList();
}
public PsiElement getGotoDeclarationTarget(final PsiElement element, final PsiElement navElement) {
return navElement;
}
public boolean includeSelfInGotoImplementation(@NotNull final PsiElement element) {
final TargetElementEvaluator elementEvaluator = targetElementEvaluator.forLanguage(element.getLanguage());
return elementEvaluator == null || elementEvaluator.includeSelfInGotoImplementation(element);
}
protected final LanguageExtension<TargetElementEvaluator> targetElementEvaluator = new LanguageExtension<TargetElementEvaluator>("com.intellij.targetElementEvaluator");
public boolean acceptImplementationForReference(PsiReference reference, PsiElement element) {
return true;
}
public SearchScope getSearchScope(Editor editor, PsiElement element) {
return PsiSearchHelper.SERVICE.getInstance(element.getProject()).getUseScope(element);
}
}