IDEA-218163 Move target file locations sorting to FileReferenceHelper implementations

GitOrigin-RevId: c7ecb5fa09bf22bfc21ede6b33277038d04189d7
This commit is contained in:
Yuriy Artamonov
2019-07-23 20:18:03 +03:00
committed by intellij-monorepo-bot
parent 6295897a6b
commit 1b985b2313
10 changed files with 329 additions and 199 deletions
@@ -70,12 +70,32 @@ public abstract class FileReferenceHelper {
@NotNull
public abstract Collection<PsiFileSystemItem> getContexts(final Project project, @NotNull final VirtualFile file);
// JavaDoc
/**
* Provides file target contexts, locations where users can create a file, depending on passed {@code file}.
*
* @param project project
* @param file file
* @return target contexts
*/
@NotNull
public Collection<FileTargetContext> getTargetContexts(Project project, @NotNull VirtualFile file) {
return ContainerUtil.map(getContexts(project, file), FileTargetContext::new);
}
/**
* Sorts target contexts depending on passed {@code file}. This sorting affects UI of quick fixes that create files in target locations.
*
* @param project project
* @param file file
* @param targetContexts available targets
* @return sorted targets collection
*/
@NotNull
public Collection<FileTargetContext> sortTargetContexts(Project project, @NotNull VirtualFile file,
@NotNull Collection<FileTargetContext> targetContexts) {
return targetContexts;
}
public abstract boolean isMine(final Project project, @NotNull final VirtualFile file);
public boolean isFallback() {
@@ -411,17 +411,30 @@ public class FileReferenceSet {
PsiFile file = getContainingFile();
if (file == null) return Collections.emptyList();
Collection<FileTargetContext> result;
Collection<PsiFileSystemItem> contexts = getCustomizationContexts(file);
if (contexts != null) {
return toTargetContexts(contexts);
result = toTargetContexts(contexts);
}
if (isAbsolutePathReference()) {
else if (isAbsolutePathReference()) {
Collection<PsiFileSystemItem> locations = getAbsoluteTopLevelDirLocations(file);
return toTargetContexts(locations);
result = toTargetContexts(locations);
}
else {
result = getTargetContextByFile(file);
}
return getTargetContextByFile(file);
return sortTargetContexts(file, result);
}
private static Collection<FileTargetContext> sortTargetContexts(PsiFile file, Collection<FileTargetContext> targetContexts) {
for (FileReferenceHelper helper : FileReferenceHelperRegistrar.getHelpers()) {
if (helper.isMine(file.getProject(), file.getVirtualFile())) {
return helper.sortTargetContexts(file.getProject(), file.getVirtualFile(), targetContexts);
}
}
return targetContexts;
}
@Nullable
@@ -471,7 +484,7 @@ public class FileReferenceSet {
Collection<FileTargetContext> contexts = helper.getTargetContexts(project, virtualFile);
for (FileTargetContext context : contexts) {
list.add(context);
hasRealContexts |= !(context.getContext() instanceof FileReferenceResolver);
hasRealContexts |= !(context.getFileSystemItem() instanceof FileReferenceResolver);
}
}
}
@@ -9,23 +9,35 @@ import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Collections;
// todo JavaDoc
/**
* Describes existing or non-existing location in file system where new files can be created.
*/
public class FileTargetContext {
private final PsiFileSystemItem myContext;
private final String[] myPathToCreate;
// todo JavaDoc
/**
* Constructs new target context.
*
* @param context file system item that will be used as target directory
* @param pathToCreate additional existing or non-existing paths
*/
public FileTargetContext(@NotNull PsiFileSystemItem context, @NotNull String[] pathToCreate) {
myContext = context;
myPathToCreate = pathToCreate;
}
/**
* Constructs new target context.
*
* @param context file system item that will be used as target directory
*/
public FileTargetContext(@NotNull PsiFileSystemItem context) {
this(context, ArrayUtil.EMPTY_STRING_ARRAY);
}
public PsiFileSystemItem getContext() {
public PsiFileSystemItem getFileSystemItem() {
return myContext;
}
@@ -4,30 +4,28 @@ package com.intellij.codeInsight.daemon.quickFix;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement;
import com.intellij.icons.AllIcons;
import com.intellij.ide.projectView.impl.ProjectRootsUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.roots.SourceFolder;
import com.intellij.openapi.roots.impl.ProjectFileIndexImpl;
import com.intellij.openapi.ui.popup.*;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.IconUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.PlatformIcons;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.java.JavaResourceRootType;
import org.jetbrains.jps.model.java.JavaSourceRootType;
import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
import javax.swing.*;
import java.util.List;
@@ -48,15 +46,13 @@ public abstract class AbstractCreateFileFix extends LocalQuickFixAndIntentionAct
protected long myIsAvailableTimeStamp;
protected AbstractCreateFileFix(@Nullable PsiElement element,
String newFileName,
List<TargetDirectory> directories,
String[] subPath,
@NotNull NewFileLocation newFileLocation,
@NotNull String fixLocaleKey) {
super(element);
myNewFileName = newFileName;
myDirectories = directories;
mySubPath = subPath;
myNewFileName = newFileLocation.getNewFileName();
myDirectories = newFileLocation.getDirectories();
mySubPath = newFileLocation.getSubPath();
myKey = fixLocaleKey;
}
@@ -98,15 +94,12 @@ public abstract class AbstractCreateFileFix extends LocalQuickFixAndIntentionAct
return;
}
List<TargetDirectoryListItem> sortedDirectories = getTargetDirectoryListItems(directories);
sortWithResourcePriority(file, sortedDirectories);
if (editor == null) {
// run on first item of sorted list in batch mode
apply(myStartElement.getProject(), directories.get(0));
}
else {
showOptionsPopup(project, editor, sortedDirectories);
showOptionsPopup(project, editor, directories);
}
}
}
@@ -114,20 +107,6 @@ public abstract class AbstractCreateFileFix extends LocalQuickFixAndIntentionAct
protected abstract void apply(@NotNull Project project, TargetDirectory directory) throws IncorrectOperationException;
// todo move sorting and source sets logic to extension point like FileReferenceHelper ?
protected void sortWithResourcePriority(@NotNull PsiFile file, List<TargetDirectoryListItem> sortedDirectories) {
// sort only if we have resource roots
if (sortedDirectories.stream().anyMatch(AbstractCreateFileFix::isResourceRoot)) {
ProjectFileIndex projectFileIndex = ProjectFileIndex.getInstance(file.getProject());
if (projectFileIndex.isInTestSourceContent(file.getVirtualFile())) {
sortedDirectories.sort(AbstractCreateFileFix::compareTargetsForTests);
}
else if (projectFileIndex.isInSourceContent(file.getVirtualFile())) {
sortedDirectories.sort(AbstractCreateFileFix::compareTargetsForProduction);
}
}
}
protected static PsiDirectory findOrCreateSubdirectory(PsiDirectory directory, String subDirectoryName) {
PsiDirectory existingDirectory = directory.findSubdirectory(subDirectoryName);
if (existingDirectory == null) {
@@ -138,7 +117,9 @@ public abstract class AbstractCreateFileFix extends LocalQuickFixAndIntentionAct
protected void showOptionsPopup(@NotNull Project project,
@NotNull Editor editor,
List<TargetDirectoryListItem> items) {
List<TargetDirectory> directories) {
List<TargetDirectoryListItem> items = getTargetDirectoryListItems(directories);
String filePath = myNewFileName;
if (mySubPath.length > 0) {
filePath = StringUtil.join(mySubPath, VFS_SEPARATOR_CHAR + "") + VFS_SEPARATOR_CHAR + myNewFileName;
@@ -148,14 +129,20 @@ public abstract class AbstractCreateFileFix extends LocalQuickFixAndIntentionAct
new BaseListPopupStep<TargetDirectoryListItem>(CodeInsightBundle.message(myKey, filePath), items) {
@Override
public Icon getIconFor(TargetDirectoryListItem value) {
JpsModuleSourceRootType type = value.getSourceRootType();
PsiDirectory directory = value.getTarget().getDirectory();
if (directory == null) {
return PlatformIcons.FOLDER_ICON;
}
if (isSourceItem(type)) return AllIcons.Modules.SourceRoot;
if (isTestSourceItem(type)) return AllIcons.Nodes.TestSourceFolder;
if (isResourceItem(type)) return AllIcons.Modules.ResourcesRoot;
if (isTestResourceItem(type)) return AllIcons.Modules.TestResourcesRoot;
VirtualFile file = directory.getVirtualFile();
return PlatformIcons.FOLDER_ICON;
ProjectFileIndexImpl projectFileIndex = (ProjectFileIndexImpl)ProjectRootManager.getInstance(project).getFileIndex();
SourceFolder sourceFolder = projectFileIndex.getSourceFolder(file);
if (sourceFolder != null && sourceFolder.getFile() != null) {
return IconUtil.getIcon(sourceFolder.getFile(), 0, project);
}
return IconUtil.getIcon(file, 0, project);
}
@NotNull
@@ -193,61 +180,12 @@ public abstract class AbstractCreateFileFix extends LocalQuickFixAndIntentionAct
PsiDirectory d = targetDirectory.getDirectory();
assert d != null : "Invalid PsiDirectory instances found";
ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(d.getProject()).getFileIndex();
Module targetModule = projectFileIndex.getModuleForFile(d.getVirtualFile());
JpsModuleSourceRootType sourceRootType = null;
if (targetModule != null) {
SourceFolder folder = getSourceFolder(targetModule, d);
if (folder != null) {
sourceRootType = folder.getRootType();
}
}
String presentablePath = getPresentableContentRootPath(d.getProject(), d.getVirtualFile(), targetDirectory.getPathToCreate());
return new TargetDirectoryListItem(targetDirectory, sourceRootType, presentablePath);
return new TargetDirectoryListItem(targetDirectory, presentablePath);
});
}
private static int getTestsTargetOrdinal(TargetDirectoryListItem item) {
JpsModuleSourceRootType type = item.getSourceRootType();
if (isSourceItem(type)) return 4;
if (isTestSourceItem(type)) return 3;
if (isResourceItem(type)) return 2;
if (isTestResourceItem(type)) return 1;
return 0;
}
private static int getSourcesTargetOrdinal(TargetDirectoryListItem item) {
JpsModuleSourceRootType type = item.getSourceRootType();
if (isTestSourceItem(type)) return 4;
if (isSourceItem(type)) return 3;
if (isTestResourceItem(type)) return 2;
if (isResourceItem(type)) return 1;
return 0;
}
private static boolean isTestResourceItem(@Nullable JpsModuleSourceRootType type) {
return type == JavaResourceRootType.TEST_RESOURCE;
}
private static boolean isResourceItem(@Nullable JpsModuleSourceRootType type) {
return type == JavaResourceRootType.RESOURCE;
}
private static boolean isTestSourceItem(@Nullable JpsModuleSourceRootType type) {
return type == JavaSourceRootType.TEST_SOURCE;
}
private static boolean isSourceItem(@Nullable JpsModuleSourceRootType type) {
return type == JavaSourceRootType.SOURCE;
}
@NotNull
private static String getPresentableContentRootPath(@NotNull Project project,
@NotNull VirtualFile f,
@@ -260,63 +198,16 @@ public abstract class AbstractCreateFileFix extends LocalQuickFixAndIntentionAct
return toProjectPath;
}
protected static int compareTargetsForTests(@NotNull TargetDirectoryListItem d1, @NotNull TargetDirectoryListItem d2) {
int o1 = getTestsTargetOrdinal(d1);
int o2 = getTestsTargetOrdinal(d2);
if (o1 > 0 && o2 > 0) {
return Integer.compare(o1, o2);
}
return compareDirectoryPaths(d1, d2);
}
protected static int compareTargetsForProduction(@NotNull TargetDirectoryListItem d1, @NotNull TargetDirectoryListItem d2) {
int o1 = getSourcesTargetOrdinal(d1);
int o2 = getSourcesTargetOrdinal(d2);
if (o1 > 0 && o2 > 0) {
return Integer.compare(o1, o2);
}
return compareDirectoryPaths(d1, d2);
}
private static int compareDirectoryPaths(@NotNull TargetDirectoryListItem d1, @NotNull TargetDirectoryListItem d2) {
PsiDirectory directory1 = d1.getTarget().getDirectory();
PsiDirectory directory2 = d2.getTarget().getDirectory();
assert directory1 != null : "Invalid PsiDirectory instances found";
assert directory2 != null : "Invalid PsiDirectory instances found";
VirtualFile f1 = directory1.getVirtualFile();
VirtualFile f2 = directory2.getVirtualFile();
return f1.getPath().compareTo(f2.getPath());
}
protected static boolean isResourceRoot(TargetDirectoryListItem d) {
return isResourceItem(d.getSourceRootType()) ||
isTestResourceItem(d.getSourceRootType());
}
protected static class TargetDirectoryListItem {
private final TargetDirectory myTargetDirectory;
private final JpsModuleSourceRootType mySourceRootType;
private final String myPresentablePath;
public TargetDirectoryListItem(@NotNull TargetDirectory targetDirectory,
@Nullable JpsModuleSourceRootType type,
@NotNull String presentablePath) {
myTargetDirectory = targetDirectory;
mySourceRootType = type;
myPresentablePath = presentablePath;
}
@Nullable
private JpsModuleSourceRootType getSourceRootType() {
return mySourceRootType;
}
private String getPresentablePath() {
return myPresentablePath;
}
@@ -325,19 +216,4 @@ public abstract class AbstractCreateFileFix extends LocalQuickFixAndIntentionAct
return myTargetDirectory;
}
}
@Nullable
private static SourceFolder getSourceFolder(@NotNull Module module, @NotNull PsiDirectory directory) {
ContentEntry[] entries = ModuleRootManager.getInstance(module).getContentEntries();
for (ContentEntry contentEntry : entries) {
for (SourceFolder sourceFolder : contentEntry.getSourceFolders()) {
if (sourceFolder.getFile() != null
&& VfsUtilCore.isAncestor(sourceFolder.getFile(), directory.getVirtualFile(), false)) {
return sourceFolder;
}
}
}
return null;
}
}
@@ -10,27 +10,26 @@ import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* Quick fix that creates a new directory in one of the target directories. Automatically creates all intermediate directories of
* {@link TargetDirectory#getPathToCreate()} and {@link NewFileLocation#getSubPath()}. If there are multiple target directories it shows
* a popup where users can select desired target directory.
*/
public class CreateDirectoryFix extends AbstractCreateFileFix {
// invoked from other module
@SuppressWarnings("WeakerAccess")
public CreateDirectoryFix(@NotNull PsiElement psiElement,
@NotNull List<TargetDirectory> directories,
@NotNull String[] subPath,
@NotNull String newDirectoryName,
@NotNull NewFileLocation newFileLocation,
@NotNull String fixLocaleKey) {
super(psiElement, newDirectoryName, directories, subPath, fixLocaleKey);
super(psiElement, newFileLocation, fixLocaleKey);
myIsAvailable = true;
myIsAvailableTimeStamp = System.currentTimeMillis();
}
public CreateDirectoryFix(@NotNull PsiElement psiElement,
@NotNull List<TargetDirectory> directories,
@NotNull String[] subPath,
@NotNull String newDirectoryName) {
this(psiElement, directories, subPath, newDirectoryName, "create.directory.text");
@NotNull NewFileLocation newFileLocation) {
this(psiElement, newFileLocation, "create.directory.text");
}
@Override
@@ -26,43 +26,40 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.List;
import java.util.function.Supplier;
/**
* Quick fix that creates a new file in one of the target directories. Automatically creates all intermediate directories of
* {@link TargetDirectory#getPathToCreate()} and {@link NewFileLocation#getSubPath()}. If there are multiple target directories it shows
* a popup where users can select desired target directory.
*/
public class CreateFileWithScopeFix extends AbstractCreateFileFix {
private final String myText;
@Nullable
private Supplier<String> myFileTextSupplier;
// todo JavaDoc
// invoked from other module
@SuppressWarnings("WeakerAccess")
public CreateFileWithScopeFix(@NotNull PsiElement psiElement,
@NotNull List<TargetDirectory> directories,
@NotNull String[] subPath,
@NotNull String newFileName,
@NotNull NewFileLocation newFileLocation,
@Nullable String fileText,
@NotNull String fixLocaleKey) {
super(psiElement, newFileName, directories, subPath, fixLocaleKey);
super(psiElement, newFileLocation, fixLocaleKey);
myText = fileText;
myIsAvailable = !FileTypeManager.getInstance().getFileTypeByFileName(newFileName).isBinary();
myIsAvailable = !FileTypeManager.getInstance().getFileTypeByFileName(myNewFileName).isBinary();
myIsAvailableTimeStamp = System.currentTimeMillis();
}
public CreateFileWithScopeFix(@NotNull PsiElement psiElement,
@NotNull List<TargetDirectory> directories,
@NotNull String[] subPath,
@NotNull String newFileName) {
this(psiElement, directories, subPath, newFileName, null, "create.file.text");
@NotNull NewFileLocation newFileLocation) {
this(psiElement, newFileLocation, null, "create.file.text");
}
public CreateFileWithScopeFix(@NotNull PsiElement psiElement,
@NotNull List<TargetDirectory> directories,
@NotNull String[] subPath,
@NotNull String newFileName,
@NotNull NewFileLocation newFileLocation,
@NotNull Supplier<String> fileTextSupplier) {
this(psiElement, directories, subPath, newFileName, null, "create.file.text");
this(psiElement, newFileLocation, null, "create.file.text");
myFileTextSupplier = fileTextSupplier;
}
@@ -79,14 +79,15 @@ public class FileReferenceQuickFixProvider {
List<TargetDirectory> targetDirectories = getTargets(reference, module, newFileName, false);
if (targetDirectories.isEmpty()) return emptyList();
return singletonList(new MyCreateFileFix(element, targetDirectories, getPathToReferencePart(reference), newFileName,
reference.getNewFileTemplateName()));
NewFileLocation location = new NewFileLocation(targetDirectories, getPathToReferencePart(reference), newFileName);
return singletonList(new MyCreateFileFix(element, location, reference.getNewFileTemplateName()));
}
else {
List<TargetDirectory> targetDirectories = getTargets(reference, module, newFileName, true);
if (targetDirectories.isEmpty()) return emptyList();
return singletonList(new CreateDirectoryFix(element, targetDirectories, getPathToReferencePart(reference), newFileName));
NewFileLocation location = new NewFileLocation(targetDirectories, getPathToReferencePart(reference), newFileName);
return singletonList(new CreateDirectoryFix(element, location));
}
}
@@ -100,7 +101,7 @@ public class FileReferenceQuickFixProvider {
List<TargetDirectory> targetDirectories = new SmartList<>();
for (FileTargetContext targetContext : contexts) {
PsiFileSystemItem context = targetContext.getContext();
PsiFileSystemItem context = targetContext.getFileSystemItem();
VirtualFile virtualFile = context.getVirtualFile();
if (virtualFile == null || !virtualFile.isValid()) continue;
@@ -215,7 +216,7 @@ public class FileReferenceQuickFixProvider {
SmartList<FileTargetContext> contexts = new SmartList<>();
for (FileTargetContext targetContext : targetContexts) {
if (targetContext != null) {
PsiFileSystemItem fsContext = targetContext.getContext();
PsiFileSystemItem fsContext = targetContext.getFileSystemItem();
VirtualFile virtualFile = fsContext.getVirtualFile();
if (virtualFile != null && fsContext.isDirectory() && virtualFile.isInLocalFileSystem()) {
if (module != null) {
@@ -246,11 +247,9 @@ public class FileReferenceQuickFixProvider {
private final String myNewFileTemplateName;
private MyCreateFileFix(@NotNull PsiElement psiElement,
@NotNull List<TargetDirectory> directories,
@NotNull String[] subPath,
@NotNull String newFileName,
@NotNull NewFileLocation newFileLocation,
@Nullable String newFileTemplateName) {
super(psiElement, directories, subPath, newFileName);
super(psiElement, newFileLocation);
myNewFileTemplateName = newFileTemplateName;
}
@@ -0,0 +1,55 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.daemon.quickFix;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* Describes possible locations for new file / directory that can be created by a quick fix.
*
* @see CreateFileWithScopeFix
* @see CreateDirectoryFix
*/
public class NewFileLocation {
private final List<TargetDirectory> myDirectories;
private final String[] mySubPath;
private final String myNewFileName;
public NewFileLocation(List<TargetDirectory> directories, String newFileName) {
this(directories, ArrayUtil.EMPTY_STRING_ARRAY, newFileName);
}
public NewFileLocation(@NotNull List<TargetDirectory> targetDirectories,
@NotNull String[] subPath,
@NotNull String newFileName) {
myDirectories = targetDirectories;
mySubPath = subPath;
myNewFileName = newFileName;
}
/**
* @return target directories where sub path and new file can be created
*/
@NotNull
public List<TargetDirectory> getDirectories() {
return myDirectories;
}
/**
* @return intermediate path to new file that may not exist but should be created when a quick fix applied
*/
@NotNull
public String[] getSubPath() {
return mySubPath;
}
/**
* @return new file name
*/
@NotNull
public String getNewFileName() {
return myNewFileName;
}
}
@@ -8,6 +8,12 @@ import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Describes target directory for quick fixes that create new files.
*
* @see CreateDirectoryFix
* @see CreateFileWithScopeFix
*/
public class TargetDirectory {
private final SmartPsiElementPointer<PsiDirectory> myDirectory;
@NotNull
@@ -21,6 +21,7 @@ import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.impl.DirectoryIndex;
import com.intellij.openapi.roots.impl.ProjectFileIndexImpl;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
@@ -31,9 +32,13 @@ import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Query;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes;
import org.jetbrains.jps.model.java.JavaResourceRootType;
import org.jetbrains.jps.model.java.JavaSourceRootType;
import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
import java.util.*;
import java.util.stream.Collectors;
@@ -69,6 +74,13 @@ public class PsiFileReferenceHelper extends FileReferenceHelper {
return getContextsForModule(module, "", module.getModuleWithDependenciesScope());
}
@NotNull
@Override
public Collection<FileTargetContext> sortTargetContexts(Project project, @NotNull VirtualFile file,
@NotNull Collection<FileTargetContext> targetContexts) {
return sortWithResourcePriority(project, file, targetContexts);
}
@NotNull
@Override
public Collection<FileTargetContext> getTargetContexts(@NotNull Project project, @NotNull VirtualFile file) {
@@ -135,6 +147,109 @@ public class PsiFileReferenceHelper extends FileReferenceHelper {
return Collections.emptyList();
}
private static Collection<FileTargetContext> sortWithResourcePriority(@NotNull Project project, @NotNull VirtualFile file,
@NotNull Collection<FileTargetContext> targetContexts) {
// here we try to sort target locations depending on src/test origin
if (targetContexts.isEmpty() || targetContexts.size() == 1) {
return targetContexts;
}
List<FileTargetContextWrapper> targetContextWrappers = findSourceRootTypes(targetContexts);
// sort only if we have resource roots
if (ContainerUtil.find(targetContextWrappers, PsiFileReferenceHelper::isResourceRoot) == null) {
return targetContexts;
}
// if file is under sources root then src/resources directories at the top
// if file is under test sources root then test/resources directories at the top
ProjectFileIndex projectFileIndex = ProjectFileIndex.getInstance(project);
if (projectFileIndex.isInTestSourceContent(file)) {
targetContextWrappers.sort(PsiFileReferenceHelper::compareTargetsForTests);
}
else if (projectFileIndex.isInSourceContent(file)) {
targetContextWrappers.sort(PsiFileReferenceHelper::compareTargetsForProduction);
}
return ContainerUtil.map(targetContextWrappers, FileTargetContextWrapper::getTargetContext);
}
private static List<FileTargetContextWrapper> findSourceRootTypes(Collection<FileTargetContext> targetContexts) {
return ContainerUtil.map(targetContexts, c -> {
Project project = c.getFileSystemItem().getProject();
SourceFolder sourceFolder = getSourceFolder(project, c.getFileSystemItem().getVirtualFile());
return new FileTargetContextWrapper(c, sourceFolder != null ? sourceFolder.getRootType() : null);
});
}
@Nullable
private static SourceFolder getSourceFolder(@NotNull Project project, @NotNull VirtualFile directory) {
ProjectFileIndexImpl projectFileIndex = (ProjectFileIndexImpl)ProjectRootManager.getInstance(project).getFileIndex();
return projectFileIndex.getSourceFolder(directory);
}
private static int compareTargetsForTests(@NotNull FileTargetContextWrapper d1, @NotNull FileTargetContextWrapper d2) {
int o1 = getTestsTargetOrdinal(d1);
int o2 = getTestsTargetOrdinal(d2);
if (o1 > 0 && o2 > 0) {
return Integer.compare(o1, o2);
}
return compareDirectoryPaths(d1, d2);
}
private static int compareTargetsForProduction(@NotNull FileTargetContextWrapper d1, @NotNull FileTargetContextWrapper d2) {
int o1 = getSourcesTargetOrdinal(d1);
int o2 = getSourcesTargetOrdinal(d2);
if (o1 > 0 && o2 > 0) {
return Integer.compare(o1, o2);
}
return compareDirectoryPaths(d1, d2);
}
private static int getTestsTargetOrdinal(@NotNull FileTargetContextWrapper item) {
JpsModuleSourceRootType type = item.getSourceRootType();
if (isSourceItem(type)) return 4;
if (isTestSourceItem(type)) return 3;
if (isResourceItem(type)) return 2;
if (isTestResourceItem(type)) return 1;
return 0;
}
private static int getSourcesTargetOrdinal(@NotNull FileTargetContextWrapper item) {
JpsModuleSourceRootType type = item.getSourceRootType();
if (isTestSourceItem(type)) return 4;
if (isSourceItem(type)) return 3;
if (isTestResourceItem(type)) return 2;
if (isResourceItem(type)) return 1;
return 0;
}
private static boolean isResourceRoot(@NotNull FileTargetContextWrapper d) {
return isResourceItem(d.getSourceRootType()) ||
isTestResourceItem(d.getSourceRootType());
}
private static int compareDirectoryPaths(@NotNull FileTargetContextWrapper d1, @NotNull FileTargetContextWrapper d2) {
PsiFileSystemItem directory1 = d1.getTargetContext().getFileSystemItem();
PsiFileSystemItem directory2 = d2.getTargetContext().getFileSystemItem();
assert directory1 != null : "Invalid PsiFileSystemItem instances found";
assert directory2 != null : "Invalid PsiFileSystemItem instances found";
VirtualFile f1 = directory1.getVirtualFile();
VirtualFile f2 = directory2.getVirtualFile();
return f1.getPath().compareTo(f2.getPath());
}
private static String[] removeCommonStartPackages(String[] path, String packagePath) {
List<String> packages = StringUtil.split(packagePath, ".");
List<String> result = new SmartList<>();
@@ -161,23 +276,27 @@ public class PsiFileReferenceHelper extends FileReferenceHelper {
@NotNull
private static String[] getRelativePath(VirtualFile file, VirtualFile root) {
LinkedList<String> names = new LinkedList<>();
List<String> names = new ArrayList<>();
VirtualFile parent = file;
while (parent != null
&& !parent.equals(root)) {
names.addFirst(parent.getName());
names.add(parent.getName());
parent = parent.getParent();
}
return ArrayUtil.toStringArray(names);
if (names.isEmpty()) return ArrayUtil.EMPTY_STRING_ARRAY;
String[] path = new String[names.size()];
for (int i = 0; i < names.size(); i++) {
path[i] = names.get(names.size() - i - 1);
}
return path;
}
@NotNull
private static List<SourceFolder> getMissingTargetFolders(Module module, Collection<PsiFileSystemItem> contextsForModule) {
// todo comment
// find additional source folders that can be used to create a file, e.g. they do not have the exact package, but it can be created
ModuleRootModel model = ModuleRootManager.getInstance(module);
return Arrays.stream(model.getContentEntries())
@@ -278,4 +397,38 @@ public class PsiFileReferenceHelper extends FileReferenceHelper {
return result != null ? result:Collections.emptyList();
}
private static boolean isTestResourceItem(@Nullable JpsModuleSourceRootType type) {
return type == JavaResourceRootType.TEST_RESOURCE;
}
private static boolean isResourceItem(@Nullable JpsModuleSourceRootType type) {
return type == JavaResourceRootType.RESOURCE;
}
private static boolean isTestSourceItem(@Nullable JpsModuleSourceRootType type) {
return type == JavaSourceRootType.TEST_SOURCE;
}
private static boolean isSourceItem(@Nullable JpsModuleSourceRootType type) {
return type == JavaSourceRootType.SOURCE;
}
private static class FileTargetContextWrapper {
private final FileTargetContext myTargetContext;
private final JpsModuleSourceRootType myRootType;
private FileTargetContextWrapper(FileTargetContext context, JpsModuleSourceRootType type) {
myTargetContext = context;
myRootType = type;
}
private FileTargetContext getTargetContext() {
return myTargetContext;
}
private JpsModuleSourceRootType getSourceRootType() {
return myRootType;
}
}
}