/* * Copyright 2000-2015 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.jetbrains.lang.dart.ide.findUsages; import com.intellij.find.findUsages.FindUsagesHandler; import com.intellij.find.findUsages.FindUsagesOptions; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiComment; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiNameIdentifierOwner; import com.intellij.psi.search.LocalSearchScope; import com.intellij.psi.search.PsiSearchScopeUtil; import com.intellij.psi.search.SearchScope; import com.intellij.usageView.UsageInfo; import com.intellij.util.Processor; import com.jetbrains.lang.dart.analyzer.DartAnalysisServerService; import com.jetbrains.lang.dart.psi.DartReference; import org.dartlang.analysis.server.protocol.Location; import org.dartlang.analysis.server.protocol.SearchResult; import org.dartlang.analysis.server.protocol.SearchResultKind; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class DartServerFindUsagesHandler extends FindUsagesHandler { public DartServerFindUsagesHandler(@NotNull final PsiElement element) { super(mayBeChangeToNameIdentifier(element)); } @NotNull private static PsiElement mayBeChangeToNameIdentifier(@NotNull final PsiElement element) { if (element instanceof PsiNameIdentifierOwner) { final PsiElement nameIdentifier = ((PsiNameIdentifierOwner)element).getNameIdentifier(); if (nameIdentifier != null) return nameIdentifier; } return element; } @Override public boolean processElementUsages(@NotNull final PsiElement elementToSearch, @NotNull final Processor<UsageInfo> processor, @NotNull final FindUsagesOptions options) { final SearchScope scope = options.searchScope; final Project project = ReadAction.compute(this::getProject); final DartAnalysisServerService service = DartAnalysisServerService.getInstance(project); final ReadActionConsumer<SearchResult> searchResultProcessor = new ReadActionConsumer<SearchResult>() { @Override public void consumeInReadAction(SearchResult result) { if (result.getKind().equals(SearchResultKind.DECLARATION)) return; final Location location = result.getLocation(); final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(FileUtil.toSystemIndependentName(location.getFile())); if (vFile == null) return; if (!scope.contains(vFile)) return; final PsiFile psiFile = elementToSearch.getManager().findFile(vFile); if (psiFile == null) return; final int offset = service.getConvertedOffset(vFile, location.getOffset()); final int length = service.getConvertedOffset(vFile, location.getOffset() + location.getLength()) - offset; final TextRange range = TextRange.create(offset, offset + length); final boolean potentialUsage = result.isPotential(); final PsiElement usageElement = getUsagePsiElement(psiFile, range); final UsageInfo usageInfo = usageElement == null ? null : getUsageInfo(usageElement, range, potentialUsage); if (usageInfo != null && usageInfo.getElement() != null && (!(scope instanceof LocalSearchScope) || PsiSearchScopeUtil.isInScope((LocalSearchScope)scope, usageInfo.getElement()))) { processor.process(usageInfo); } } }; final VirtualFile file = ReadAction.compute(() -> elementToSearch.getContainingFile().getVirtualFile()); final int offset = elementToSearch.getTextRange().getStartOffset(); service.search_findElementReferences(file, offset, searchResultProcessor); return true; } @Nullable public static UsageInfo getUsageInfo(@NotNull final PsiElement usageElement, @NotNull final TextRange range, final boolean potentialUsage) { final int offset = range.getStartOffset() - usageElement.getTextRange().getStartOffset(); boolean nonCodeUsage = usageElement instanceof PsiComment || usageElement.getParent() instanceof PsiComment; final UsageInfo usageInfo = new UsageInfo(usageElement, offset, offset + range.getLength(), nonCodeUsage); usageInfo.setDynamicUsage(potentialUsage); return usageInfo; } @Nullable public static PsiElement getUsagePsiElement(@NotNull final PsiFile psiFile, @NotNull final TextRange textRange) { // try to find DartReference matching textRange. If not possible then return the topmost element matching textRange. // If neither found then return minimal element that includes the textRange. PsiElement element = psiFile.findElementAt(textRange.getStartOffset()); if (element == null) return null; boolean rangeOk = element.getTextRange().contains(textRange); if (rangeOk && element instanceof DartReference) return element; TextRange previousRange = element.getTextRange(); PsiElement parent; while ((parent = element.getParent()) != null) { final TextRange parentRange = parent.getTextRange(); if (rangeOk) { if (!parentRange.equals(previousRange)) { return element; // range became bigger, return previous that matched better } if (parent instanceof DartReference) { return parent; } else { previousRange = parentRange; element = parent; } } else { rangeOk = parent.getTextRange().contains(textRange); if (rangeOk && parent instanceof DartReference) { return parent; } else { previousRange = parentRange; element = parent; } } } return null; } }