/* * Copyright 2013-2016 consulo.io * * 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 consulo.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.navigation.NavigationItem; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.ex.EditorEx; 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 consulo.annotations.Immutable; import consulo.annotations.RequiredDispatchThread; import consulo.annotations.RequiredReadAction; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; /** * @author VISTALL * @since 20.04.2015 */ public class TargetElementUtil { @NotNull @Immutable public static Set<String> getAllAccepted() { Set<String> flags = new LinkedHashSet<String>(); TargetElementUtilEx.EP_NAME.composite().collectAllAccepted(flags); return flags; } @NotNull @Immutable public static Set<String> getDefinitionSearchFlags() { Set<String> flags = new LinkedHashSet<String>(); TargetElementUtilEx.EP_NAME.composite().collectDefinitionSearchFlags(flags); return flags; } @NotNull @Immutable public static Set<String> getReferenceSearchFlags() { Set<String> flags = new LinkedHashSet<String>(); TargetElementUtilEx.EP_NAME.composite().collectReferenceSearchFlags(flags); return flags; } @Nullable public static PsiReference findReference(Editor editor) { int offset = editor.getCaretModel().getOffset(); PsiReference result = findReference(editor, offset); if (result == null) { int expectedCaretOffset = editor instanceof EditorEx ? ((EditorEx)editor).getExpectedCaretOffset() : offset; if (expectedCaretOffset != offset) { result = findReference(editor, expectedCaretOffset); } } return result; } /** * @param document * @param offset * @return * @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; } @Nullable public static PsiElement adjustReference(@NotNull PsiReference ref) { return TargetElementUtilEx.EP_NAME.composite().adjustReference(ref); } @Nullable @RequiredReadAction public static PsiReference findReference(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); } @Nullable @RequiredDispatchThread public static PsiElement findTargetElement(Editor editor, @NotNull Set<String> flags) { ApplicationManager.getApplication().assertIsDispatchThread(); int offset = editor.getCaretModel().getOffset(); final PsiElement result = findTargetElement(editor, flags, offset); if (result != null) return result; int expectedCaretOffset = editor instanceof EditorEx ? ((EditorEx)editor).getExpectedCaretOffset() : offset; if (expectedCaretOffset != offset) { return findTargetElement(editor, flags, expectedCaretOffset); } return null; } @Nullable public static PsiElement findTargetElement(@NotNull Editor editor, @NotNull Set<String> flags, int offset) { PsiElement targetElement = findTargetElementImpl(editor, flags, offset); if (targetElement == null) { return null; } PsiElement target = TargetElementUtilEx.EP_NAME.composite().modifyTargetElement(targetElement, flags); if (target != null) { return target; } return targetElement; } @Nullable private static PsiElement findTargetElementImpl(@NotNull Editor editor, @NotNull Set<String> flags, int offset) { Project project = editor.getProject(); if (project == null) return null; if (flags.contains(TargetElementUtilEx.LOOKUP_ITEM_ACCEPTED)) { 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.contains(TargetElementUtilEx.REFERENCED_ELEMENT_ACCEPTED)) { 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.contains(TargetElementUtilEx.ELEMENT_NAME_ACCEPTED)) { if (element instanceof PsiNamedElement) return element; return getNamedElement(element, offset - element.getTextRange().getStartOffset()); } return null; } @Nullable private static PsiElement getReferenceOrReferencedElement(PsiFile file, Editor editor, Set<String> flags, int offset) { PsiElement referenceOrReferencedElement = getReferenceOrReferencedElementImpl(file, editor, flags, offset); PsiElement psiElement = TargetElementUtilEx.EP_NAME.composite().modifyReferenceOrReferencedElement(referenceOrReferencedElement, file, editor, flags, offset); if (psiElement != null) { return psiElement; } return referenceOrReferencedElement; } @Nullable private static PsiElement getReferenceOrReferencedElementImpl(PsiFile file, Editor editor, Set<String> flags, int offset) { PsiReference ref = findReference(editor, offset); if (ref == null) return null; PsiElement referenceOrReferencedElement = TargetElementUtilEx.EP_NAME.composite().getReferenceOrReferencedElement(ref, flags); if (referenceOrReferencedElement != null) { return referenceOrReferencedElement; } 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; } } @Nullable private static PsiElement getTargetElementFromLookup(Project project) { Lookup activeLookup = LookupManager.getInstance(project).getActiveLookup(); if (activeLookup != null) { LookupElement item = activeLookup.getCurrentItem(); final PsiElement psi = item == null ? null : CompletionUtil.getTargetElement(item); if (psi != null && psi.isValid()) { return psi; } } return null; } private static boolean isAcceptableReferencedElement(final PsiElement element, final PsiElement referenceOrReferencedElement) { if(referenceOrReferencedElement == null || !referenceOrReferencedElement.isValid()) { return false; } if(!TargetElementUtilEx.EP_NAME.composite().isAcceptableReferencedElement(element, referenceOrReferencedElement)) { return false; } return true; } @Nullable public static 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 private static 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; } } if(element == null) { return null; } return TargetElementUtilEx.EP_NAME.composite().getNamedElement(element); } @Nullable public static PsiElement adjustElement(final Editor editor, final Set<String> flags, final PsiElement element, final PsiElement contextElement) { return TargetElementUtilEx.EP_NAME.composite().adjustElement(editor, flags, element, contextElement); } public static boolean inVirtualSpace(@NotNull Editor editor, int offset) { if (offset == editor.getCaretModel().getOffset()) { return EditorUtil.inVirtualSpace(editor, editor.getCaretModel().getLogicalPosition()); } return false; } private static boolean isIdentifierPart(@Nullable PsiFile file, CharSequence text, int offset) { if (file != null) { if (TargetElementUtilEx.EP_NAME.composite().isIdentifierPart(file, text, offset)) { return true; } } return Character.isJavaIdentifierPart(text.charAt(offset)); } public static boolean acceptImplementationForReference(PsiReference reference, PsiElement element) { return true; } @NotNull public static 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); } Collection<PsiElement> targetCandidates = TargetElementUtilEx.EP_NAME.composite().getTargetCandidates(reference); if (targetCandidates != null) { return targetCandidates; } return Collections.emptyList(); } @Nullable public static PsiElement getGotoDeclarationTarget(final PsiElement element, final PsiElement navElement) { PsiElement gotoDeclarationTarget = TargetElementUtilEx.EP_NAME.composite().getGotoDeclarationTarget(element, navElement); if (gotoDeclarationTarget != null) { return gotoDeclarationTarget; } return navElement; } public static boolean includeSelfInGotoImplementation(final PsiElement element) { return TargetElementUtilEx.EP_NAME.composite().includeSelfInGotoImplementation(element); } public static SearchScope getSearchScope(Editor editor, PsiElement element) { return PsiSearchHelper.SERVICE.getInstance(element.getProject()).getUseScope(element); } }