mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-242675 Opening folder without .idea creates corrupted project
GitOrigin-RevId: a75f221451f0964d29464bb7a3308cff1b049f0f
This commit is contained in:
committed by
intellij-monorepo-bot
parent
beff07f3ae
commit
8c31a5c569
@@ -347,8 +347,8 @@ public final class VfsUtil extends VfsUtilCore {
|
||||
public static @Nullable VirtualFile createDirectoryIfMissing(@NotNull String directoryPath) throws IOException {
|
||||
return createDirectoryIfMissing(LocalFileSystem.getInstance(), directoryPath);
|
||||
}
|
||||
|
||||
public static @Nullable VirtualFile createDirectoryIfMissing(@NotNull VirtualFileSystem fileSystem,
|
||||
|
||||
public static @Nullable VirtualFile createDirectoryIfMissing(@NotNull VirtualFileSystem fileSystem,
|
||||
@NotNull String directoryPath) throws IOException {
|
||||
String path = FileUtil.toSystemIndependentName(directoryPath);
|
||||
VirtualFile file = fileSystem.refreshAndFindFileByPath(path);
|
||||
@@ -409,7 +409,7 @@ public final class VfsUtil extends VfsUtilCore {
|
||||
}
|
||||
|
||||
public static @Nullable VirtualFile getUserHomeDir() {
|
||||
final String path = SystemProperties.getUserHome();
|
||||
String path = SystemProperties.getUserHome();
|
||||
return LocalFileSystem.getInstance().findFileByPath(FileUtil.toSystemIndependentName(path));
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -318,7 +318,7 @@ public class CopyFilesOrDirectoriesDialog extends RefactoringDialog implements D
|
||||
Messages.showErrorDialog(myProject, RefactoringBundle.message("cannot.create.directory"), RefactoringBundle.message("error.title"));
|
||||
return;
|
||||
}
|
||||
FileChooserUtil.setLastOpenedFile(myProject, myTargetDirectory.getVirtualFile());
|
||||
FileChooserUtil.setLastOpenedFile(myProject, myTargetDirectory.getVirtualFile().toNioPath());
|
||||
|
||||
try {
|
||||
for (PsiElement element : myElements) {
|
||||
|
||||
@@ -21,8 +21,10 @@ import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.wm.IdeFocusManager;
|
||||
import com.intellij.openapi.wm.IdeFrame;
|
||||
import com.intellij.platform.CommandLineProjectOpenProcessor;
|
||||
import com.intellij.platform.PlatformProjectOpenProcessor;
|
||||
import com.intellij.pom.Navigatable;
|
||||
import com.intellij.util.PlatformUtils;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -44,8 +46,10 @@ public final class CommandLineProcessor {
|
||||
|
||||
private CommandLineProcessor() { }
|
||||
|
||||
private static @NotNull CommandLineProcessorResult doOpenFileOrProject(Path file, boolean shouldWait) {
|
||||
OpenProjectTask openProjectOptions = new OpenProjectTask();
|
||||
// public for testing
|
||||
@ApiStatus.Internal
|
||||
public static @NotNull CommandLineProcessorResult doOpenFileOrProject(@NotNull Path file, boolean shouldWait) {
|
||||
OpenProjectTask openProjectOptions = PlatformProjectOpenProcessor.createOptionsToOpenDotIdeaOrCreateNewIfNotExists(file, null);
|
||||
// do not check for .ipr files in specified directory (@develar: it is existing behaviour, I am not fully sure that it is correct)
|
||||
openProjectOptions.checkDirectoryForFileBasedProjects = false;
|
||||
Project project = ProjectUtil.openOrImport(file, openProjectOptions);
|
||||
|
||||
@@ -3,9 +3,11 @@ package com.intellij.ide
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.util.concurrent.Future
|
||||
|
||||
internal data class CommandLineProcessorResult(val project: Project?, val future: Future<CliResult>) {
|
||||
@ApiStatus.Internal
|
||||
data class CommandLineProcessorResult(val project: Project?, val future: Future<CliResult>) {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun createError(message: String): CommandLineProcessorResult {
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.intellij.ide.lightEdit.LightEditUtil;
|
||||
import com.intellij.ide.util.PsiNavigationSupport;
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.fileChooser.FileChooser;
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
|
||||
@@ -35,6 +36,7 @@ import com.intellij.platform.PlatformProjectOpenProcessor;
|
||||
import com.intellij.projectImport.ProjectAttachProcessor;
|
||||
import com.intellij.projectImport.ProjectOpenProcessor;
|
||||
import com.intellij.util.PlatformUtils;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -49,9 +51,9 @@ import static com.intellij.ide.lightEdit.LightEditFeatureUsagesUtil.OpenPlace.We
|
||||
public class OpenFileAction extends AnAction implements DumbAware, LightEditCompatible {
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
final Project project = e.getProject();
|
||||
final boolean showFiles = project != null || PlatformProjectOpenProcessor.getInstanceIfItExists() != null;
|
||||
final FileChooserDescriptor descriptor = showFiles ? new ProjectOrFileChooserDescriptor() : new ProjectOnlyFileChooserDescriptor();
|
||||
Project project = e.getProject();
|
||||
boolean showFiles = project != null || PlatformProjectOpenProcessor.getInstanceIfItExists() != null;
|
||||
FileChooserDescriptor descriptor = showFiles ? new ProjectOrFileChooserDescriptor() : new ProjectOnlyFileChooserDescriptor();
|
||||
|
||||
VirtualFile toSelect = null;
|
||||
if (StringUtil.isNotEmpty(GeneralSettings.getInstance().getDefaultProjectDirectory())) {
|
||||
@@ -98,19 +100,7 @@ public class OpenFileAction extends AnAction implements DumbAware, LightEditComp
|
||||
private static void doOpenFile(@Nullable Project project, @NotNull VirtualFile file) {
|
||||
Path filePath = file.toNioPath();
|
||||
if (Files.isDirectory(filePath)) {
|
||||
boolean canAttach = ProjectAttachProcessor.canAttachToProject();
|
||||
boolean preferAttach = PlatformUtils.isDataGrip() && project != null && canAttach && !ProjectUtil.isValidProjectPath(filePath);
|
||||
Project openedProject;
|
||||
if (preferAttach && PlatformProjectOpenProcessor.attachToProject(project, filePath, null)) {
|
||||
return;
|
||||
}
|
||||
else if (canAttach) {
|
||||
openedProject = ProjectManagerEx.getInstanceEx().openProject(filePath, PlatformProjectOpenProcessor.createOptionsToOpenDotIdeaOrCreateNewIfNotExists(filePath, project));
|
||||
}
|
||||
else {
|
||||
openedProject = ProjectUtil.openOrImport(filePath, OpenProjectTask.withProjectToClose(project));
|
||||
}
|
||||
FileChooserUtil.setLastOpenedFile(openedProject, file);
|
||||
openExistingDir(filePath, project);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -123,7 +113,7 @@ public class OpenFileAction extends AnAction implements DumbAware, LightEditComp
|
||||
else if (answer == Messages.YES) {
|
||||
Project openedProject = ProjectUtil.openOrImport(filePath, OpenProjectTask.withProjectToClose(project));
|
||||
if (openedProject != null) {
|
||||
FileChooserUtil.setLastOpenedFile(openedProject, file);
|
||||
FileChooserUtil.setLastOpenedFile(openedProject, filePath);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -146,6 +136,28 @@ public class OpenFileAction extends AnAction implements DumbAware, LightEditComp
|
||||
}
|
||||
}
|
||||
|
||||
// public for testing
|
||||
@ApiStatus.Internal
|
||||
public static @Nullable Project openExistingDir(@NotNull Path file, @Nullable Project currentProject) {
|
||||
Project openedProject;
|
||||
boolean canAttach = ProjectAttachProcessor.canAttachToProject();
|
||||
boolean preferAttach = currentProject != null && canAttach && PlatformUtils.isDataGrip() && !ProjectUtil.isValidProjectPath(file);
|
||||
if (preferAttach && PlatformProjectOpenProcessor.attachToProject(currentProject, file, null)) {
|
||||
return null;
|
||||
}
|
||||
else if (canAttach) {
|
||||
OpenProjectTask options = PlatformProjectOpenProcessor.createOptionsToOpenDotIdeaOrCreateNewIfNotExists(file, currentProject);
|
||||
openedProject = ProjectManagerEx.getInstanceEx().openProject(file, options);
|
||||
}
|
||||
else {
|
||||
openedProject = ProjectUtil.openOrImport(file, OpenProjectTask.withProjectToClose(currentProject));
|
||||
}
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
FileChooserUtil.setLastOpenedFile(openedProject, file);
|
||||
}
|
||||
return openedProject;
|
||||
}
|
||||
|
||||
@Messages.YesNoCancelResult
|
||||
private static int shouldOpenNewProject(@Nullable Project project, @NotNull VirtualFile file) {
|
||||
if (file.getFileType() instanceof ProjectFileType) {
|
||||
|
||||
@@ -80,5 +80,5 @@ data class OpenProjectTask(val forceOpenInNewFrame: Boolean = false,
|
||||
|
||||
/** Used only by [ProjectUtil.openOrImport] */
|
||||
@JvmField
|
||||
var checkDirectoryForFileBasedProjects = true
|
||||
internal var checkDirectoryForFileBasedProjects = true
|
||||
}
|
||||
@@ -159,7 +159,8 @@ public final class ProjectUtil {
|
||||
}
|
||||
|
||||
if (isValidProjectPath(file)) {
|
||||
return ProjectManagerEx.getInstanceEx().openProject(file, options);
|
||||
// see OpenProjectTest.`open valid existing project dir with inability to attach using OpenFileAction` test about why `runConfigurators = true` is specified here
|
||||
return ProjectManagerEx.getInstanceEx().openProject(file, options.withRunConfigurators());
|
||||
}
|
||||
|
||||
if (options.checkDirectoryForFileBasedProjects && Files.isDirectory(file)) {
|
||||
|
||||
+4
-2
@@ -1,4 +1,4 @@
|
||||
// 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.
|
||||
// Copyright 2000-2020 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.openapi.fileChooser.ex;
|
||||
|
||||
import com.intellij.icons.AllIcons;
|
||||
@@ -159,7 +159,9 @@ public class FileChooserDialogImpl extends DialogWrapper implements FileChooserD
|
||||
}
|
||||
|
||||
void storeSelection(@Nullable VirtualFile file) {
|
||||
FileChooserUtil.setLastOpenedFile(myProject, file);
|
||||
if (file != null) {
|
||||
FileChooserUtil.setLastOpenedFile(myProject, file.toNioPath());
|
||||
}
|
||||
if (file != null && file.getFileSystem() instanceof LocalFileSystem) {
|
||||
saveRecent(file.getPath());
|
||||
}
|
||||
|
||||
+18
-1
@@ -8,6 +8,7 @@ import com.intellij.openapi.fileChooser.PathChooserDialog;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.project.ProjectUtil;
|
||||
import com.intellij.openapi.util.SystemInfo;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VfsUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
@@ -16,6 +17,7 @@ import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
public final class FileChooserUtil {
|
||||
@@ -33,8 +35,14 @@ public final class FileChooserUtil {
|
||||
return path != null ? LocalFileSystem.getInstance().findFileByPath(path) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #setLastOpenedFile(Project, Path)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static void setLastOpenedFile(@Nullable Project project, @Nullable VirtualFile file) {
|
||||
if (file == null) return;
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
if (project == null) {
|
||||
PropertiesComponent.getInstance().setValue(LAST_OPENED_FILE_PATH, file.getPath());
|
||||
}
|
||||
@@ -43,6 +51,15 @@ public final class FileChooserUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static void setLastOpenedFile(@Nullable Project project, @NotNull Path file) {
|
||||
if (project == null) {
|
||||
PropertiesComponent.getInstance().setValue(LAST_OPENED_FILE_PATH, FileUtil.toSystemIndependentName(file.toString()));
|
||||
}
|
||||
else if (!project.isDisposed()) {
|
||||
PropertiesComponent.getInstance(project).setValue(LAST_OPENED_FILE_PATH, FileUtil.toSystemIndependentName(file.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static VirtualFile getFileToSelect(@NotNull FileChooserDescriptor descriptor, @Nullable Project project,
|
||||
@Nullable VirtualFile toSelect, @Nullable VirtualFile lastPath) {
|
||||
|
||||
@@ -190,19 +190,25 @@ class PlatformProjectOpenProcessor : ProjectOpenProcessor(), CommandLineProjectO
|
||||
}
|
||||
|
||||
/**
|
||||
* If project file in IDEA format (.idea directory or .ipr file) exists, just open it.
|
||||
* If project file in IDEA format (.idea directory or .ipr file) exists, open it and run configurators if no modules.
|
||||
*
|
||||
* If doesn't exists, create a new project using default project template and run configurators (something that creates module).
|
||||
* (at the moment of creation project file in IDEA format will be removed if any).
|
||||
*
|
||||
* This method must be not used in tests.
|
||||
*
|
||||
* See OpenProjectTest.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
@JvmStatic
|
||||
fun createOptionsToOpenDotIdeaOrCreateNewIfNotExists(projectDir: Path, projectToClose: Project?): OpenProjectTask {
|
||||
val validProjectPath = ProjectUtil.isValidProjectPath(projectDir)
|
||||
// doesn't make sense to use default project as template in tests
|
||||
return OpenProjectTask(runConfigurators = !validProjectPath,
|
||||
isNewProject = !validProjectPath,
|
||||
// doesn't make sense to refresh or to use default project as template in tests
|
||||
val isUnitTestMode = ApplicationManager.getApplication().isUnitTestMode
|
||||
return OpenProjectTask(runConfigurators = true,
|
||||
isNewProject = !ProjectUtil.isValidProjectPath(projectDir),
|
||||
projectToClose = projectToClose,
|
||||
useDefaultProjectAsTemplate = !ApplicationManager.getApplication().isUnitTestMode)
|
||||
isRefreshVfsNeeded = !isUnitTestMode,
|
||||
useDefaultProjectAsTemplate = !isUnitTestMode)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright 2000-2020 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.openapi.project
|
||||
|
||||
import com.intellij.ide.CommandLineProcessor
|
||||
import com.intellij.ide.actions.OpenFileAction
|
||||
import com.intellij.openapi.module.ModuleManager
|
||||
import com.intellij.platform.ModuleAttachProcessor
|
||||
import com.intellij.projectImport.ProjectAttachProcessor
|
||||
import com.intellij.testFramework.*
|
||||
import com.intellij.testFramework.assertions.Assertions.assertThat
|
||||
import com.intellij.util.io.createDirectories
|
||||
import org.junit.ClassRule
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import java.nio.file.Path
|
||||
|
||||
// terms:
|
||||
// valid: .idea exists
|
||||
// clean: .idea doesn't exists
|
||||
// existing: project directory exists
|
||||
|
||||
// with ability to attach - there is some defined ProjectAttachProcessor extension (e.g. WS, PS).
|
||||
// with inability to attach - there is no any defined ProjectAttachProcessor extension (e.g. IU, IC).
|
||||
|
||||
@RunWith(Parameterized::class)
|
||||
internal class OpenProjectTest(private val opener: Opener) {
|
||||
companion object {
|
||||
@JvmField
|
||||
@ClassRule
|
||||
val appRule = ApplicationRule()
|
||||
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters(name = "{0}")
|
||||
fun params(): Iterable<Opener> {
|
||||
return listOf(
|
||||
Opener("OpenFileAction") { OpenFileAction.openExistingDir(it, null)!! },
|
||||
Opener("CLI") { CommandLineProcessor.doOpenFileOrProject(it, false).project!! }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmField
|
||||
@Rule
|
||||
val tempDir = TemporaryDirectory()
|
||||
|
||||
@JvmField
|
||||
@Rule
|
||||
val disposableRule = DisposableRule()
|
||||
|
||||
@Test
|
||||
fun `open valid existing project dir with ability to attach`() {
|
||||
ExtensionTestUtil.maskExtensions(ProjectAttachProcessor.EP_NAME, listOf(ModuleAttachProcessor()), disposableRule.disposable)
|
||||
val projectDir = tempDir.newPath("project")
|
||||
projectDir.resolve(".idea").createDirectories()
|
||||
openUsingOpenFileActionAndAssertThatProjectContainsOneModule(projectDir)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `open clean existing project dir with ability to attach`() {
|
||||
ExtensionTestUtil.maskExtensions(ProjectAttachProcessor.EP_NAME, listOf(ModuleAttachProcessor()), disposableRule.disposable)
|
||||
val projectDir = tempDir.newPath("project")
|
||||
projectDir.createDirectories()
|
||||
openUsingOpenFileActionAndAssertThatProjectContainsOneModule(projectDir)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `open valid existing project dir with inability to attach`() {
|
||||
// Regardless of product (Idea vs PhpStorm), if .idea directory exists, but no modules, we must run configurators to add some module.
|
||||
// Maybe not fully clear why it is performed as part of project opening and silently, but it is existing behaviour.
|
||||
// So, existing behaviour should be preserved and any changes should be done not as part of task "use unified API to open project", but separately later.
|
||||
ExtensionTestUtil.maskExtensions(ProjectAttachProcessor.EP_NAME, listOf(), disposableRule.disposable)
|
||||
val projectDir = tempDir.newPath("project")
|
||||
projectDir.resolve(".idea").createDirectories()
|
||||
openUsingOpenFileActionAndAssertThatProjectContainsOneModule(projectDir)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `open clean existing project dir with inability to attach`() {
|
||||
ExtensionTestUtil.maskExtensions(ProjectAttachProcessor.EP_NAME, listOf(), disposableRule.disposable)
|
||||
val projectDir = tempDir.newPath("project")
|
||||
projectDir.createDirectories()
|
||||
openUsingOpenFileActionAndAssertThatProjectContainsOneModule(projectDir)
|
||||
}
|
||||
|
||||
private fun openUsingOpenFileActionAndAssertThatProjectContainsOneModule(projectDir: Path) {
|
||||
val project = opener.opener(projectDir)!!
|
||||
try {
|
||||
assertThatProjectContainsOneModule(project)
|
||||
}
|
||||
finally {
|
||||
PlatformTestUtil.forceCloseProjectWithoutSaving(project)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class Opener(private val name: String, val opener: (Path) -> Project?) {
|
||||
override fun toString() = name
|
||||
}
|
||||
|
||||
private fun assertThatProjectContainsOneModule(project: Project) {
|
||||
assertThat(ModuleManager.getInstance(project).modules).hasSize(1)
|
||||
}
|
||||
Reference in New Issue
Block a user