diff --git a/platform/platform-impl/src/com/intellij/ide/impl/ProjectUtil.kt b/platform/platform-impl/src/com/intellij/ide/impl/ProjectUtil.kt index f9844bdd8f85..ca77dda34559 100644 --- a/platform/platform-impl/src/com/intellij/ide/impl/ProjectUtil.kt +++ b/platform/platform-impl/src/com/intellij/ide/impl/ProjectUtil.kt @@ -188,11 +188,15 @@ object ProjectUtil { } } - val storePathManager = serviceAsync() - val descriptor = withContext(Dispatchers.IO) { - storePathManager.getStoreDescriptor(file) - } - if (descriptor.testStoreDirectoryExistsForProjectRoot()) { + // `isDirectory` test here is for backward compatibility with 252: in 252 we never entered this method with regular files - only with + // directories. The problem here is that any regular file now has a storeDescriptor (getStoreDescriptor return type is not nullable), + // which evaluates to `regularFile.parent.resolve(".idea")`, which, if exists, pushes IDE to open a new project, instead of a file in + // the opened project. This is a tiny quick-fix for 253. We should rework the project open flow to avoid this strange check here. + if (Files.isDirectory(file) && isValidProjectPath(file)) { + val descriptor = withContext(Dispatchers.IO) { + serviceAsync().getStoreDescriptor(file) + } + LOG.info("Opening existing project with .idea at $file") // see OpenProjectTest.`open valid existing project dir with inability to attach using OpenFileAction` test about why `runConfigurators = true` is specified here val options = options.copy( diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/project/OpenProjectTest.kt b/platform/platform-tests/testSrc/com/intellij/openapi/project/OpenProjectTest.kt index 3cdc6ba804be..107ec91e322e 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/project/OpenProjectTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/openapi/project/OpenProjectTest.kt @@ -6,6 +6,7 @@ import com.intellij.ide.impl.ProjectUtil import com.intellij.ide.impl.ProjectUtil.FolderOpeningMode.AS_FOLDER import com.intellij.ide.impl.ProjectUtil.FolderOpeningMode.AS_PROJECT import com.intellij.ide.impl.SelectProjectOpenProcessorDialog +import com.intellij.openapi.project.TestOpenMode.ModeFileOrFolderDefault import com.intellij.openapi.project.TestOpenMode.ModeFolderAsFolder import com.intellij.openapi.project.TestOpenMode.ModeFolderAsProject import com.intellij.openapi.project.TestProjectSource.SourceCLI @@ -41,15 +42,15 @@ import kotlin.io.path.writeText // clean: .idea doesn't exists // existing: project directory exists // nested: .idea exists and ../.idea exists too -// multibuild: does not exist, and there are 2 marker build files (pom.xml and build.gradle) -// TODO: open regular file +// multibuild: .idea does not exist, and there are 2 marker build files (pom.xml and build.gradle) +// regular file: regular file that is not a folder // 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). enum class TestProjectSource { SourceOpenFileAction, SourceCLI } -enum class TestOpenMode { ModeFolderAsProject, ModeFolderAsFolder } +enum class TestOpenMode { ModeFileOrFolderDefault, ModeFolderAsProject, ModeFolderAsFolder } @RunWith(Parameterized::class) internal class OpenProjectTest(private val opener: Opener) { @@ -79,7 +80,7 @@ internal class OpenProjectTest(private val opener: Opener) { // I don't have strong opinion about defaultProjectTemplateShouldBeAppliedOverride. // Weak opinion: a folder is not a project => we don't need default project settings. // Feel free to change the test if you have strong opinion about desired behavior. - Opener(SourceCLI, ModeFolderAsFolder, expectedModules = emptyList(), expectedRoots = listOf($$"$ROOT$"), defaultProjectTemplateShouldBeAppliedOverride = false) { + Opener(SourceCLI, ModeFileOrFolderDefault, expectedModules = emptyList(), expectedRoots = listOf($$"$ROOT$"), defaultProjectTemplateShouldBeAppliedOverride = false) { runBlocking { CommandLineProcessor.doOpenFileOrProject(it, createOrOpenExistingProject = false, false) }.project!! }, ) @@ -152,9 +153,9 @@ internal class OpenProjectTest(private val opener: Opener) { @Test fun `open multibuild existing project dir with inability to attach`() = runBlocking(Dispatchers.Default) { Assume.assumeTrue( - "This test does not handle ProjectAsProject mode yet, because `null` from SelectProjectOpenProcessorDialog" + + "This test does not handle ModeFolderAsProject mode yet, because `null` from SelectProjectOpenProcessorDialog" + " has different behavior when opening folder from CLI and from open action, and we don't want to cement this behavior in tests.", - opener.mode == ModeFolderAsFolder, + opener.mode != ModeFolderAsProject, ) val processorNames = ProjectOpenProcessor.EXTENSION_POINT_NAME.extensionList.map(ProjectOpenProcessor::name) @@ -171,6 +172,41 @@ internal class OpenProjectTest(private val opener: Opener) { } } + @Test + fun `open project then open regular file in the same valid existing project dir with inability to attach`() = runBlocking(Dispatchers.Default) { + Assume.assumeTrue( + "Ignore ModeFolderAsProject/ModeFolderAsFolder, because we are checking open of regular files here, not folders", + opener.mode != ModeFolderAsProject && opener.mode != ModeFolderAsFolder, + ) + + val projectDir = tempDir.newPath("project") + projectDir.resolve(".idea").createDirectories() + val javaFile = projectDir.resolve("MyClass.java") + javaFile.writeText("public class MyClass {}") + + ExtensionTestUtil.maskExtensions(ProjectAttachProcessor.EP_NAME, listOf(), disposableRule.disposable) + opener.opener(projectDir)!!.useProject { openedProject -> + val project = opener.opener(javaFile) + // the file should be opened in the already opened project + assertThat(project).isSameAs(openedProject) + } + Unit + } + + @Test + fun `open project then open the the same valid existing project dir with inability to attach`() = runBlocking(Dispatchers.Default) { + val projectDir = tempDir.newPath("project") + projectDir.resolve(".idea").createDirectories() + + ExtensionTestUtil.maskExtensions(ProjectAttachProcessor.EP_NAME, listOf(), disposableRule.disposable) + opener.opener(projectDir)!!.useProject { openedProject -> + val project = opener.opener(projectDir) + // this should bring already opened project to foreground + assertThat(project).isSameAs(openedProject) + } + Unit + } + private fun setupMultibuildProject(): Path { val projectDir = tempDir.newPath("project") projectDir.createDirectories()