package com.jetbrains.lang.dart.ide.actions; import com.google.common.collect.Sets; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.QueryExecutorBase; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.search.SearchScope; import com.intellij.psi.search.searches.DefinitionsScopedSearch; import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.CachedValuesManager; import com.intellij.psi.util.PsiModificationTracker; import com.intellij.util.Processor; import com.jetbrains.lang.dart.DartComponentType; import com.jetbrains.lang.dart.DartLanguage; import com.jetbrains.lang.dart.analyzer.DartAnalysisServerService; import com.jetbrains.lang.dart.ide.hierarchy.DartHierarchyUtil; import com.jetbrains.lang.dart.psi.DartComponent; import com.jetbrains.lang.dart.psi.DartComponentName; import com.jetbrains.lang.dart.util.DartResolveUtil; import org.dartlang.analysis.server.protocol.Element; import org.dartlang.analysis.server.protocol.Location; import org.dartlang.analysis.server.protocol.TypeHierarchyItem; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; public class DartInheritorsSearcher extends QueryExecutorBase<PsiElement, DefinitionsScopedSearch.SearchParameters> { @Override public void processQuery(@NotNull final DefinitionsScopedSearch.SearchParameters parameters, @NotNull final Processor<PsiElement> consumer) { final Ref<VirtualFile> fileRef = Ref.create(); final Ref<Integer> offsetRef = Ref.create(); final Ref<DartComponentType> componentTypeRef = Ref.create(); prepare(parameters, fileRef, offsetRef, componentTypeRef); if (fileRef.isNull() || offsetRef.isNull() || componentTypeRef.isNull()) return; ApplicationManager.getApplication().runReadAction(() -> { final List<TypeHierarchyItem> hierarchyItems = CachedValuesManager.getCachedValue(parameters.getElement(), () -> { final DartAnalysisServerService das = DartAnalysisServerService.getInstance(parameters.getElement().getProject()); final List<TypeHierarchyItem> items = das.search_getTypeHierarchy(fileRef.get(), offsetRef.get(), false); return new CachedValueProvider.Result<>(items, PsiModificationTracker.MODIFICATION_COUNT); }); final List<DartComponent> components = componentTypeRef.get() == DartComponentType.CLASS ? getSubClasses(parameters.getElement().getProject(), parameters.getScope(), hierarchyItems) : getSubMembers(parameters.getElement().getProject(), parameters.getScope(), hierarchyItems); for (DartComponent component : components) { consumer.process(component); } }); } private static void prepare(@NotNull final DefinitionsScopedSearch.SearchParameters parameters, @NotNull final Ref<VirtualFile> fileRef, @NotNull final Ref<Integer> offsetRef, @NotNull final Ref<DartComponentType> componentTypeRef) { ApplicationManager.getApplication().runReadAction(() -> { final PsiElement element = parameters.getElement(); if (element.getLanguage() != DartLanguage.INSTANCE) return; final DartComponentType componentType = DartComponentType.typeOf(element); if (componentType != DartComponentType.CLASS && componentType != DartComponentType.METHOD && componentType != DartComponentType.OPERATOR) { return; } final DartComponentName componentName = element instanceof DartComponentName ? (DartComponentName)element : element instanceof DartComponent ? ((DartComponent)element).getComponentName() : null; final VirtualFile file = componentName == null ? null : DartResolveUtil.getRealVirtualFile(componentName.getContainingFile()); if (file != null) { fileRef.set(file); offsetRef.set(componentName.getTextRange().getStartOffset()); componentTypeRef.set(componentType); } }); } @NotNull public static List<DartComponent> getSubClasses(@NotNull final Project project, @NotNull final SearchScope scope, @NotNull final List<TypeHierarchyItem> hierarchyItems) { if (hierarchyItems.isEmpty()) return Collections.emptyList(); final List<DartComponent> result = new ArrayList<>(hierarchyItems.size()); addSubClasses(project, scope, Sets.newHashSet(), hierarchyItems, result, hierarchyItems.get(0), false); return result; } @NotNull public static List<DartComponent> getSubMembers(@NotNull final Project project, @NotNull final SearchScope scope, @NotNull final List<TypeHierarchyItem> hierarchyItems) { if (hierarchyItems.isEmpty()) return Collections.emptyList(); final List<DartComponent> result = new ArrayList<>(hierarchyItems.size()); addSubMembers(project, scope, Sets.newHashSet(), hierarchyItems, result, hierarchyItems.get(0), false); return result; } private static void addSubClasses(@NotNull final Project project, @NotNull final SearchScope scope, @NotNull final Set<TypeHierarchyItem> visited, @NotNull final List<TypeHierarchyItem> hierarchyItems, @NotNull final List<DartComponent> components, @NotNull final TypeHierarchyItem currentItem, final boolean addItem) { if (!visited.add(currentItem)) { return; } if (addItem) { final Element element = currentItem.getClassElement(); final Location location = element.getLocation(); final DartComponent component = DartHierarchyUtil.findDartComponent(project, location); if (component != null && isInScope(scope, component)) { components.add(component); } } for (int subIndex : currentItem.getSubclasses()) { final TypeHierarchyItem subItem = hierarchyItems.get(subIndex); addSubClasses(project, scope, visited, hierarchyItems, components, subItem, true); } } private static void addSubMembers(@NotNull final Project project, @NotNull final SearchScope scope, @NotNull final Set<TypeHierarchyItem> visited, @NotNull final List<TypeHierarchyItem> hierarchyItems, @NotNull final List<DartComponent> components, @NotNull final TypeHierarchyItem currentItem, final boolean addItem) { if (!visited.add(currentItem)) { return; } if (addItem) { final Element element = currentItem.getMemberElement(); if (element != null) { final Location location = element.getLocation(); final DartComponent component = DartHierarchyUtil.findDartComponent(project, location); if (component != null && isInScope(scope, component)) { components.add(component); } } } for (int subIndex : currentItem.getSubclasses()) { final TypeHierarchyItem subItem = hierarchyItems.get(subIndex); addSubMembers(project, scope, visited, hierarchyItems, components, subItem, true); } } private static boolean isInScope(@NotNull final SearchScope scope, @NotNull final PsiElement element) { final VirtualFile file = element.getContainingFile().getVirtualFile(); if (file == null) return false; return scope.contains(file); } }