package com.jetbrains.lang.dart.resolve; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.impl.source.resolve.ResolveCache; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.SmartList; import com.jetbrains.lang.dart.analyzer.DartAnalysisServerService; import com.jetbrains.lang.dart.analyzer.DartServerData; import com.jetbrains.lang.dart.analyzer.DartServerData.DartNavigationRegion; import com.jetbrains.lang.dart.analyzer.DartServerData.DartNavigationTarget; import com.jetbrains.lang.dart.psi.*; import com.jetbrains.lang.dart.util.DartResolveUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; public class DartResolver implements ResolveCache.AbstractResolver<DartReference, List<? extends PsiElement>> { public static final DartResolver INSTANCE = new DartResolver(); @Nullable @Override public List<? extends PsiElement> resolve(@NotNull DartReference reference, boolean incompleteCode) { reference = replaceQualifiedReferenceWithLast(reference); final PsiFile refPsiFile = reference.getContainingFile(); int refOffset = reference.getTextRange().getStartOffset(); int refLength = reference.getTextRange().getLength(); DartNavigationRegion region = findRegion(refPsiFile, refOffset, refLength); if (region == null && reference instanceof DartLibraryId) { // DAS returns the whole "part of foo" as a region, but we have only "foo" as a reference final PsiElement parent = reference.getParent(); if (parent instanceof DartPartOfStatement) { refOffset = parent.getTextRange().getStartOffset(); refLength = reference.getTextRange().getEndOffset() - refOffset; region = findRegion(refPsiFile, refOffset, refLength); } } if (region == null && (reference instanceof DartSuperExpression || reference instanceof DartReferenceExpression)) { // DAS from SDK 1.13- returns 'super.foo' as a single range; SDK 1.14 returns 'super' and 'foo' separately. final PsiElement parent = reference.getParent(); if (parent instanceof DartSuperCallOrFieldInitializer) { final List<DartExpression> expressions = ((DartSuperCallOrFieldInitializer)parent).getExpressionList(); if (expressions.size() == 2 && expressions.get(0) instanceof DartSuperExpression && expressions.get(1) instanceof DartReferenceExpression) { refOffset = expressions.get(0).getTextRange().getStartOffset(); refLength = expressions.get(1).getTextRange().getEndOffset() - refOffset; region = findRegion(refPsiFile, refOffset, refLength); } } } if (region != null) { final Project project = reference.getProject(); final List<PsiElement> result = new SmartList<>(); for (DartNavigationTarget target : region.getTargets()) { final PsiElement targetElement = getElementForNavigationTarget(project, target); if (targetElement != null) { result.add(targetElement); } } return result; } return null; } /** * When parameter information is requested for <code>items.insert(^)</code>, * we are given <code>items.insert</code>, but we cannot resolve it, we need just <code>insert</code>. */ @NotNull private static DartReference replaceQualifiedReferenceWithLast(@NotNull DartReference reference) { final PsiElement lastChild = reference.getLastChild(); if (lastChild instanceof DartReference) { reference = (DartReference)lastChild; } return reference; } @Nullable public static DartNavigationRegion findRegion(final PsiFile refPsiFile, final int refOffset, final int refLength) { final VirtualFile refVirtualFile = DartResolveUtil.getRealVirtualFile(refPsiFile); if (refVirtualFile != null) { final List<DartServerData.DartNavigationRegion> regions = DartAnalysisServerService.getInstance(refPsiFile.getProject()).getNavigation(refVirtualFile); return findRegion(regions, refOffset, refLength); } return null; } @Nullable public static PsiElement getElementForNavigationTarget(Project project, DartNavigationTarget target) { String targetPath = target.getFile(); PsiFile file = findPsiFile(project, targetPath); if (file != null) { int targetOffset = target.getOffset(project, file.getVirtualFile()); PsiElement elementAt = PsiTreeUtil.findElementOfClassAtOffset(file, targetOffset, DartComponentName.class, false); if (elementAt == null) elementAt = PsiTreeUtil.findElementOfClassAtOffset(file, targetOffset, DartLibraryNameElement.class, false); return elementAt; } return null; } @Nullable public static PsiFile findPsiFile(@NotNull Project project, @NotNull String path) { VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(path); if (virtualFile != null) { return PsiManager.getInstance(project).findFile(virtualFile); } return null; } /** * Find the region with the given offset in the given list of sorted regions. * Returns the found region or null. */ @Nullable public static DartNavigationRegion findRegion(@NotNull final List<DartServerData.DartNavigationRegion> regions, final int offset, final int length) { int low = 0; int high = regions.size() - 1; while (low <= high) { int mid = (low + high) >>> 1; DartServerData.DartNavigationRegion midVal = regions.get(mid); int cmp = midVal.getOffset() - offset; if (cmp < 0) { low = mid + 1; } else if (cmp > 0) { high = mid - 1; } else { if (midVal.getLength() == length) { return midVal; } return null; } } return null; } }