mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-218163 Move target file locations sorting to FileReferenceHelper implementations
GitOrigin-RevId: c7ecb5fa09bf22bfc21ede6b33277038d04189d7
This commit is contained in:
committed by
intellij-monorepo-bot
parent
6295897a6b
commit
1b985b2313
+21
-1
@@ -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() {
|
||||
|
||||
+19
-6
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-3
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+26
-150
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+9
-10
@@ -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
|
||||
|
||||
+12
-15
@@ -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;
|
||||
}
|
||||
|
||||
+8
-9
@@ -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
|
||||
|
||||
+158
-5
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user