/* * 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 targets = ContainerUtil.newArrayList(); final Consumer consumer = new Consumer() { @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 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 getTargetCandidates(PsiReference reference) { if (reference instanceof PsiPolyVariantReference) { final ResolveResult[] results = ((PsiPolyVariantReference)reference).multiResolve(false); final ArrayList navigatableResults = new ArrayList(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 = new LanguageExtension("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); } }