diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/util/resource/FileResourceUtil.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/util/resource/FileResourceUtil.java index 4514ddcc2..52881f6c7 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/util/resource/FileResourceUtil.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/util/resource/FileResourceUtil.java @@ -4,6 +4,7 @@ import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.NotNullLazyValue; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; @@ -11,21 +12,26 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.util.CachedValueProvider; +import com.intellij.psi.util.CachedValuesManager; import com.intellij.util.PlatformIcons; import com.intellij.util.indexing.FileBasedIndex; import com.jetbrains.php.PhpIcons; +import fr.adrienbrault.idea.symfony2plugin.stubs.cache.FileIndexCaches; import fr.adrienbrault.idea.symfony2plugin.stubs.indexes.FileResourcesIndex; -import fr.adrienbrault.idea.symfony2plugin.util.FileResourceVisitorUtil; -import fr.adrienbrault.idea.symfony2plugin.util.PhpIndexUtil; -import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils; -import fr.adrienbrault.idea.symfony2plugin.util.SymfonyBundleUtil; +import fr.adrienbrault.idea.symfony2plugin.util.*; import fr.adrienbrault.idea.symfony2plugin.util.dict.SymfonyBundle; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.io.File; +import java.io.IOException; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; import java.util.*; import java.util.function.Supplier; +import java.util.stream.Collectors; /** * @author Daniel Espendiller @@ -49,7 +55,7 @@ public static Collection getFileResourceRefers(@NotNull Project pro * Search for files refers to given file */ @NotNull - public static Collection getFileResourceRefers(@NotNull Project project, @NotNull String bundleLocateName) { + private static Collection getFileResourceRefers(@NotNull Project project, @NotNull String bundleLocateName) { return FileBasedIndex.getInstance().getContainingFiles( FileResourcesIndex.KEY, bundleLocateName, @@ -57,6 +63,76 @@ public static Collection getFileResourceRefers(@NotNull Project pro ); } + /** + * Search for files refers to given file + */ + public static boolean hasFileResources(@NotNull Project project, @NotNull PsiFile psiFile) { + return CachedValuesManager.getCachedValue( + psiFile, + () -> { + VirtualFile virtualFile = psiFile.getVirtualFile(); + if (virtualFile == null) { + return CachedValueProvider.Result.create(Boolean.FALSE, FileIndexCaches.getModificationTrackerForIndexId(project, FileResourcesIndex.KEY)); + } + + Set collect = FileBasedIndex.getInstance().getAllKeys(FileResourcesIndex.KEY, project) + .stream() + .filter(s -> !s.startsWith("@")) + .collect(Collectors.toSet()); + + for (String s : collect) { + for (VirtualFile containingFile : FileBasedIndex.getInstance().getContainingFiles(FileResourcesIndex.KEY, s, GlobalSearchScope.allScope(project))) { + VirtualFile directory = containingFile.getParent(); + if (directory == null) { + continue; + } + + VirtualFile relativeFile = VfsUtil.findRelativeFile(directory, s.replace("\\", "/").split("/")); + if (relativeFile != null) { + String relativePath = VfsUtil.getRelativePath(virtualFile, relativeFile); + if (relativePath != null) { + return CachedValueProvider.Result.create(Boolean.TRUE, FileIndexCaches.getModificationTrackerForIndexId(project, FileResourcesIndex.KEY)); + } + } + } + } + + return CachedValueProvider.Result.create(Boolean.FALSE, FileIndexCaches.getModificationTrackerForIndexId(project, FileResourcesIndex.KEY)); + } + ); + } + + /** + * Search for files refers to given file + */ + @NotNull + public static Collection> getFileResources(@NotNull Project project, @NotNull VirtualFile virtualFile) { + Set collect = FileBasedIndex.getInstance().getAllKeys(FileResourcesIndex.KEY, project) + .stream() + .filter(s -> !s.startsWith("@")) + .collect(Collectors.toSet()); + + Collection> files = new ArrayList<>(); + for (String s : collect) { + for (VirtualFile containingFile : FileBasedIndex.getInstance().getContainingFiles(FileResourcesIndex.KEY, s, GlobalSearchScope.allScope(project))) { + VirtualFile directory = containingFile.getParent(); + if (directory == null) { + continue; + } + + VirtualFile relativeFile = VfsUtil.findRelativeFile(directory, s.replace("\\", "/").split("/")); + if (relativeFile != null) { + String relativePath = VfsUtil.getRelativePath(virtualFile, relativeFile); + if (relativePath != null) { + files.add(Pair.create(containingFile, s)); + } + } + } + } + + return files; + } + @Nullable public static String getBundleLocateName(@NotNull Project project, @NotNull VirtualFile virtualFile) { SymfonyBundle containingBundle = new SymfonyBundleUtil(project).getContainingBundle(virtualFile); @@ -104,19 +180,28 @@ public static RelatedItemLineMarkerInfo getFileImplementsLineMarker( } String bundleLocateName = FileResourceUtil.getBundleLocateName(project, virtualFile); - if(bundleLocateName == null) { - return null; - } + if(bundleLocateName != null) { + if(FileResourceUtil.getFileResourceRefers(project, bundleLocateName).size() == 0) { + return null; + } - if(FileResourceUtil.getFileResourceRefers(project, bundleLocateName).size() == 0) { - return null; - } + NavigationGutterIconBuilder builder = NavigationGutterIconBuilder.create(PhpIcons.IMPLEMENTS) + .setTargets(NotNullLazyValue.lazy(new FileResourceBundleNotNullLazyValue(project, bundleLocateName))) + .setTooltipText("Navigate to resource"); - NavigationGutterIconBuilder builder = NavigationGutterIconBuilder.create(PhpIcons.IMPLEMENTS) - .setTargets(NotNullLazyValue.lazy(new FileResourceUtil.FileResourceNotNullLazyValue(project, bundleLocateName))) - .setTooltipText("Navigate to resource"); + return builder.createLineMarkerInfo(psiFile); + } else { + if (hasFileResources(project, psiFile)) { + NavigationGutterIconBuilder builder = NavigationGutterIconBuilder.create(PhpIcons.IMPLEMENTS); + builder.setTargets(NotNullLazyValue.lazy(new FileResourceNotNullLazyValue(project, virtualFile))); - return builder.createLineMarkerInfo(psiFile); + builder.setTooltipText("Navigate to resource"); + + return builder.createLineMarkerInfo(psiFile); + } + } + + return null; } /** @@ -154,23 +239,23 @@ public static RelatedItemLineMarkerInfo getFileImplementsLineMarkerI } NavigationGutterIconBuilder builder = NavigationGutterIconBuilder.create(PlatformIcons.ANNOTATION_TYPE_ICON) - .setTargets(NotNullLazyValue.lazy(new FileResourceUtil.FileResourceNotNullLazyValue(project, names))) + .setTargets(NotNullLazyValue.lazy(new FileResourceBundleNotNullLazyValue(project, names))) .setTooltipText("Symfony: Annotation Routing"); return builder.createLineMarkerInfo(psiFile); } - private static class FileResourceNotNullLazyValue implements Supplier> { + private static class FileResourceBundleNotNullLazyValue implements Supplier> { private final Collection resources; private final Project project; - public FileResourceNotNullLazyValue(@NotNull Project project, @NotNull Collection resource) { + public FileResourceBundleNotNullLazyValue(@NotNull Project project, @NotNull Collection resource) { this.resources = resource; this.project = project; } - public FileResourceNotNullLazyValue(@NotNull Project project, @NotNull String resource) { + public FileResourceBundleNotNullLazyValue(@NotNull Project project, @NotNull String resource) { this.resources = Collections.singleton(resource); this.project = project; } @@ -264,11 +349,75 @@ public static Collection getFileResourceTargetsInDirectoryScope(@NotNul return Collections.emptyList(); } - PsiFile targetFile = PsiElementUtils.virtualFileToPsiFile(psiFile.getProject(), relativeFile); - if(targetFile == null) { - return Collections.emptyList(); + Set psiFiles = new HashSet<>(); + if (relativeFile.isDirectory()) { + String path = relativeFile.getPath(); + final PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:/**/*.php"); + + Set files = new HashSet<>(); + try { + Files.walkFileTree(Paths.get(path), new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) { + if (pathMatcher.matches(path)) { + files.add(path.toString()); + } + return files.size() < 200 ? FileVisitResult.CONTINUE : FileVisitResult.TERMINATE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) { + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException ignored) { + } + + Set collect = files.stream() + .map(s -> VfsUtil.findFileByIoFile(new File(s), false)) + .filter(Objects::nonNull) + .map(virtualFile -> PsiElementUtils.virtualFileToPsiFile(psiFile.getProject(), virtualFile)) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + psiFiles.addAll(collect); + } else { + PsiFile targetFile = PsiElementUtils.virtualFileToPsiFile(psiFile.getProject(), relativeFile); + if(targetFile != null) { + psiFiles.add(targetFile); + } + } + + return psiFiles; + } + + private static class FileResourceNotNullLazyValue implements Supplier> { + private final Project project; + private final VirtualFile virtualFile; + + public FileResourceNotNullLazyValue(Project project, VirtualFile virtualFile) { + this.project = project; + this.virtualFile = virtualFile; } - return Collections.singletonList(targetFile); + @Override + public Collection get() { + Collection psiElements = new HashSet<>(); + + for (Pair pair : getFileResources(project, virtualFile)) { + PsiFile psiFile1 = PsiElementUtils.virtualFileToPsiFile(project, pair.getFirst()); + if (psiFile1 == null) { + continue; + } + + FileResourceVisitorUtil.visitFile(psiFile1, fileResourceConsumer -> { + if (fileResourceConsumer.getResource().equalsIgnoreCase(pair.getSecond())) { + psiElements.add(fileResourceConsumer.getPsiElement()); + } + }); + } + + return psiElements; + } } }