From 6d80aab2ce087603ed51f05ee7b9a1a97e8108ff Mon Sep 17 00:00:00 2001 From: Jaime Wren Date: Wed, 3 Dec 2025 23:34:47 -0800 Subject: [PATCH 1/2] Fix IllegalStateException in ProjectOpenProcessor Migrate [FlutterProjectOpenProcessor](cci:2://file:///usr/local/google/home/jwren/src/flutter-intellij/src/io/flutter/project/FlutterProjectOpenProcessor.kt:21:0-89:1) and [FlutterStudioProjectOpenProcessor](cci:2://file:///usr/local/google/home/jwren/src/flutter-intellij/src/io/flutter/editor/FlutterStudioProjectOpenProcessor.kt:17:0-61:1) from the deprecated [doOpenProject](cci:1://file:///usr/local/google/home/jwren/src/flutter-intellij/src/io/flutter/editor/FlutterStudioProjectOpenProcessor.kt:54:2-60:3) to [openProjectAsync](cci:1://file:///usr/local/google/home/jwren/src/flutter-intellij/src/io/flutter/project/FlutterProjectOpenProcessor.kt:38:2-54:3) to fix compatibility with IntelliJ IDEA 2025.3+. This change involves: - Converting [FlutterProjectOpenProcessor](cci:2://file:///usr/local/google/home/jwren/src/flutter-intellij/src/io/flutter/project/FlutterProjectOpenProcessor.kt:21:0-89:1) to Kotlin. - Converting [FlutterStudioProjectOpenProcessor](cci:2://file:///usr/local/google/home/jwren/src/flutter-intellij/src/io/flutter/editor/FlutterStudioProjectOpenProcessor.kt:17:0-61:1) to Kotlin. - Implementing [openProjectAsync](cci:1://file:///usr/local/google/home/jwren/src/flutter-intellij/src/io/flutter/project/FlutterProjectOpenProcessor.kt:38:2-54:3) using `writeAction` for project setup. - Enabling Kotlin compilation for the main source set in [build.gradle.kts](cci:7://file:///usr/local/google/home/jwren/src/flutter-intellij/build.gradle.kts:0:0-0:0). Fixes https://github.com/flutter/flutter-intellij/issues/8629 See https://plugins.jetbrains.com/docs/intellij/project-open-processor.html --- CHANGELOG.md | 1 + build.gradle.kts | 6 + .../FlutterStudioProjectOpenProcessor.java | 57 --------- .../FlutterStudioProjectOpenProcessor.kt | 81 ++++++++++++ .../project/FlutterProjectOpenProcessor.java | 97 --------------- .../project/FlutterProjectOpenProcessor.kt | 116 ++++++++++++++++++ 6 files changed, 204 insertions(+), 154 deletions(-) delete mode 100644 src/io/flutter/editor/FlutterStudioProjectOpenProcessor.java create mode 100644 src/io/flutter/editor/FlutterStudioProjectOpenProcessor.kt delete mode 100644 src/io/flutter/project/FlutterProjectOpenProcessor.java create mode 100644 src/io/flutter/project/FlutterProjectOpenProcessor.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 67c6cab5ec..8832eb9a53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Fixed - Fixed crash when using 3rd party loggers that don't implement `setLevel`. (#8631) +- Fixed "Slow operations are prohibited on EDT" by migrating `FlutterProjectOpenProcessor` to Kotlin and using `openProjectAsync`. (#8629) ## 88.1.0 diff --git a/build.gradle.kts b/build.gradle.kts index f17d9de79b..2c70a28158 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -141,6 +141,12 @@ sourceSets { "third_party/vmServiceDrivers" ) ) + kotlin.srcDirs( + listOf( + "src", + "third_party/vmServiceDrivers" + ) + ) // Add kotlin.srcDirs if we start using Kotlin in the main plugin. resources.srcDirs( listOf( diff --git a/src/io/flutter/editor/FlutterStudioProjectOpenProcessor.java b/src/io/flutter/editor/FlutterStudioProjectOpenProcessor.java deleted file mode 100644 index a6681b5a21..0000000000 --- a/src/io/flutter/editor/FlutterStudioProjectOpenProcessor.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2018 The Chromium Authors. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ -package io.flutter.editor; - -import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.projectImport.ProjectOpenProcessor; -import com.intellij.ui.EditorNotifications; -import io.flutter.FlutterUtils; -import io.flutter.project.FlutterProjectOpenProcessor; -import io.flutter.pub.PubRoot; -import io.flutter.utils.FlutterModuleUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class FlutterStudioProjectOpenProcessor extends FlutterProjectOpenProcessor { - @Override - public @NotNull String getName() { - return "Flutter Studio"; - } - - @Override - public boolean canOpenProject(@Nullable VirtualFile file) { - if (file == null) return false; - final PubRoot root = PubRoot.forDirectory(file); - return root != null && root.declaresFlutter(); - } - - @Nullable - @Override - public Project doOpenProject(@NotNull VirtualFile virtualFile, @Nullable Project projectToClose, boolean forceOpenInNewFrame) { - final ProjectOpenProcessor importProvider = getDelegateImportProvider(virtualFile); - if (importProvider == null) return null; - - importProvider.doOpenProject(virtualFile, projectToClose, forceOpenInNewFrame); - // A callback may have caused the project to be reloaded. Find the new Project object. - Project project = FlutterUtils.findProject(virtualFile.getPath()); - if (project == null || project.isDisposed()) { - return project; - } - for (Module module : FlutterModuleUtils.getModules(project)) { - if (FlutterModuleUtils.declaresFlutter(module) && !FlutterModuleUtils.isFlutterModule(module)) { - FlutterModuleUtils.setFlutterModuleType(module); - FlutterModuleUtils.enableDartSDK(module); - } - } - project.save(); - EditorNotifications.getInstance(project).updateAllNotifications(); - - //FlutterProjectCreator.disableUserConfig(project); - return project; - } -} diff --git a/src/io/flutter/editor/FlutterStudioProjectOpenProcessor.kt b/src/io/flutter/editor/FlutterStudioProjectOpenProcessor.kt new file mode 100644 index 0000000000..fae83178f5 --- /dev/null +++ b/src/io/flutter/editor/FlutterStudioProjectOpenProcessor.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2025 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +package io.flutter.editor + +import com.intellij.openapi.application.writeAction +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.projectImport.ProjectOpenProcessor +import com.intellij.ui.EditorNotifications +import io.flutter.FlutterUtils +import io.flutter.project.FlutterProjectOpenProcessor +import io.flutter.pub.PubRoot +import io.flutter.utils.FlutterModuleUtils + +/** + * Originally `FlutterStudioProjectOpenProcessor.java`. + * + * This processor is specific to Android Studio (or when the Flutter Studio plugin is active). + * It extends `FlutterProjectOpenProcessor` to provide specialized handling for Android Studio, + * particularly ensuring that the project is correctly re-detected if the opening process causes a reload. + * + * Converted to Kotlin to support `openProjectAsync`. + */ +class FlutterStudioProjectOpenProcessor : FlutterProjectOpenProcessor() { + override val name: String + get() = "Flutter Studio" + + override fun canOpenProject(file: VirtualFile): Boolean { + val root = PubRoot.forDirectory(file) + return root != null && root.declaresFlutter() + } + + /** + * Replaces the deprecated `doOpenProject`. + * + * Performs the same logic as the original Java implementation but using `suspend` and `writeAction`. + * + * Key differences from the base class: + * - Explicitly looks up the project again using `FlutterUtils.findProject` after opening, as the project instance might have changed + * (e.g. if the platform closed and reopened it during import). + * - Ensures Dart SDK is enabled and notifications are updated. + */ + override suspend fun openProjectAsync( + virtualFile: VirtualFile, + projectToClose: Project?, + forceOpenInNewFrame: Boolean, + ): Project? { + val importProvider = getDelegateImportProvider(virtualFile) ?: return null + val project = importProvider.openProjectAsync(virtualFile, projectToClose, forceOpenInNewFrame) + + // A callback may have caused the project to be reloaded. Find the new Project object. + val newProject = FlutterUtils.findProject(virtualFile.path) + if (newProject == null || newProject.isDisposed) { + return newProject + } + + writeAction { + for (module in FlutterModuleUtils.getModules(newProject)) { + if (FlutterModuleUtils.declaresFlutter(module) && !FlutterModuleUtils.isFlutterModule(module)) { + FlutterModuleUtils.setFlutterModuleType(module) + FlutterModuleUtils.enableDartSDK(module) + } + } + newProject.save() + EditorNotifications.getInstance(newProject).updateAllNotifications() + } + + return newProject + } + + override fun doOpenProject( + virtualFile: VirtualFile, + projectToClose: Project?, + forceOpenInNewFrame: Boolean, + ): Project? { + return null + } +} diff --git a/src/io/flutter/project/FlutterProjectOpenProcessor.java b/src/io/flutter/project/FlutterProjectOpenProcessor.java deleted file mode 100644 index 69427c9a98..0000000000 --- a/src/io/flutter/project/FlutterProjectOpenProcessor.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2017 The Chromium Authors. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ -package io.flutter.project; - -import com.intellij.openapi.application.ApplicationInfo; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.projectImport.ProjectOpenProcessor; -import icons.FlutterIcons; -import io.flutter.FlutterBundle; -import io.flutter.FlutterUtils; -import io.flutter.ProjectOpenActivity; -import io.flutter.pub.PubRoot; -import io.flutter.utils.FlutterModuleUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.util.Objects; - -public class FlutterProjectOpenProcessor extends ProjectOpenProcessor { - - @Override - public @NotNull String getName() { - return FlutterBundle.message("flutter.module.name"); - } - - @Override - public Icon getIcon() { - return FlutterIcons.Flutter; - } - - @Override - public boolean canOpenProject(@Nullable VirtualFile file) { - if (file == null) return false; - final ApplicationInfo info = ApplicationInfo.getInstance(); - if (FlutterUtils.isAndroidStudio()) { - return false; - } - final PubRoot root = PubRoot.forDirectory(file); - return root != null && root.declaresFlutter(); - } - - /** - * Runs when a project is opened by selecting the project directly, possibly for import. - *

- * Doesn't run when a project is opened via recent projects menu (and so on). Actions that - * should run every time a project is opened should be in - * {@link ProjectOpenActivity} or {@link io.flutter.FlutterInitializer}. - */ - @Nullable - @Override - public Project doOpenProject(@NotNull VirtualFile file, @Nullable Project projectToClose, boolean forceOpenInNewFrame) { - // Delegate opening to the platform open processor. - final ProjectOpenProcessor importProvider = getDelegateImportProvider(file); - if (importProvider == null) return null; - - final Project project = importProvider.doOpenProject(file, projectToClose, forceOpenInNewFrame); - if (project == null || project.isDisposed()) return project; - - // Convert any modules that use Flutter but don't have IntelliJ Flutter metadata. - convertToFlutterProject(project); - - // Project gets reloaded; should this be: FlutterUtils.findProject(file.getPath()); - return project; - } - - @Nullable - protected ProjectOpenProcessor getDelegateImportProvider(@NotNull VirtualFile file) { - //noinspection DataFlowIssue - return EXTENSION_POINT_NAME.getExtensionList().stream().filter( - processor -> processor.canOpenProject(file) && !Objects.equals(processor.getName(), getName()) - ).findFirst().orElse(null); - } - - /** - * Sets up a project that doesn't have any Flutter modules. - *

- * (It probably wasn't created with "flutter create" and probably didn't have any IntelliJ configuration before.) - */ - private static void convertToFlutterProject(@NotNull Project project) { - for (Module module : FlutterModuleUtils.getModules(project)) { - if (FlutterModuleUtils.declaresFlutter(module) && !FlutterModuleUtils.isFlutterModule(module)) { - FlutterModuleUtils.setFlutterModuleAndReload(module, project); - } - } - } - - @Override - public boolean isStrongProjectInfoHolder() { - return true; - } -} diff --git a/src/io/flutter/project/FlutterProjectOpenProcessor.kt b/src/io/flutter/project/FlutterProjectOpenProcessor.kt new file mode 100644 index 0000000000..dab85dac64 --- /dev/null +++ b/src/io/flutter/project/FlutterProjectOpenProcessor.kt @@ -0,0 +1,116 @@ +/* + * Copyright 2025 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +package io.flutter.project + +import com.intellij.openapi.application.ApplicationInfo +import com.intellij.openapi.application.writeAction +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.projectImport.ProjectOpenProcessor +import icons.FlutterIcons +import io.flutter.FlutterBundle +import io.flutter.FlutterUtils +import io.flutter.pub.PubRoot +import io.flutter.utils.FlutterModuleUtils +import java.util.Objects +import javax.swing.Icon + +/** + * Originally `FlutterProjectOpenProcessor.java`. + * + * This processor handles opening Flutter projects when they are selected directly (e.g. via "Open" in the IDE). + * It delegates the actual opening to the platform's default processor (e.g. Gradle or Maven processor if applicable, + * or the generic project opener) and then ensures that any modules in the project are correctly configured as Flutter modules. + * + * Converted to Kotlin to support `openProjectAsync` which is a suspend function. + */ +open class FlutterProjectOpenProcessor : ProjectOpenProcessor() { + override val name: String + get() = FlutterBundle.message("flutter.module.name") + + override fun getIcon(file: VirtualFile): Icon? { + return FlutterIcons.Flutter + } + + override fun canOpenProject(file: VirtualFile): Boolean { + val info = ApplicationInfo.getInstance() + if (FlutterUtils.isAndroidStudio()) { + return false + } + val root = PubRoot.forDirectory(file) + return root != null && root.declaresFlutter() + } + + /** + * Replaces the deprecated `doOpenProject`. + * + * This method is `suspend` and must be used instead of `doOpenProject` to avoid `IllegalStateException` in newer IDE versions. + * + * It performs the following steps: + * 1. Finds a delegate processor (e.g. Gradle) to open the project. + * 2. Opens the project asynchronously. + * 3. Once opened, checks if the project contains Flutter modules that are not yet configured as such (e.g. missing module type). + * 4. Configures these modules as Flutter modules within a write action. + */ + override suspend fun openProjectAsync( + virtualFile: VirtualFile, + projectToClose: Project?, + forceOpenInNewFrame: Boolean, + ): Project? { + // Delegate opening to the platform open processor. + val importProvider = getDelegateImportProvider(virtualFile) ?: return null + val project = importProvider.openProjectAsync(virtualFile, projectToClose, forceOpenInNewFrame) + if (project == null || project.isDisposed) return project + + // Convert any modules that use Flutter but don't have IntelliJ Flutter metadata. + writeAction { + convertToFlutterProject(project) + } + + return project + } + + /** + * Deprecated method, kept to satisfy the compiler/interface. + * + * We return `null` to indicate that this processor does not support the synchronous opening method + * and that `openProjectAsync` should be used instead. + */ + override fun doOpenProject( + virtualFile: VirtualFile, + projectToClose: Project?, + forceOpenInNewFrame: Boolean, + ): Project? { + return null + } + + protected open fun getDelegateImportProvider(file: VirtualFile): ProjectOpenProcessor? { + return EXTENSION_POINT_NAME.extensionList.stream().filter { processor: ProjectOpenProcessor -> + processor.canOpenProject(file) && !Objects.equals( + processor.name, + name + ) + }.findFirst().orElse(null) + } + + + companion object { + /** + * Sets up a project that doesn't have any Flutter modules. + * + * + * (It probably wasn't created with "flutter create" and probably didn't have any IntelliJ configuration before.) + */ + private fun convertToFlutterProject(project: Project) { + for (module in FlutterModuleUtils.getModules(project)) { + if (FlutterModuleUtils.declaresFlutter(module) && !FlutterModuleUtils.isFlutterModule(module)) { + FlutterModuleUtils.setFlutterModuleAndReload(module, project) + } + } + } + } +} From e67a46960669a20bd259e79fec77dc9ad0ee32ae Mon Sep 17 00:00:00 2001 From: Jaime Wren Date: Mon, 8 Dec 2025 20:11:51 -0800 Subject: [PATCH 2/2] Fix threading violation in project open processors --- .../editor/FlutterStudioProjectOpenProcessor.kt | 12 ++++++------ .../flutter/project/FlutterProjectOpenProcessor.kt | 4 +--- src/io/flutter/utils/FlutterModuleUtils.java | 13 +++++++------ 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/io/flutter/editor/FlutterStudioProjectOpenProcessor.kt b/src/io/flutter/editor/FlutterStudioProjectOpenProcessor.kt index fae83178f5..d9187b27cc 100644 --- a/src/io/flutter/editor/FlutterStudioProjectOpenProcessor.kt +++ b/src/io/flutter/editor/FlutterStudioProjectOpenProcessor.kt @@ -57,16 +57,16 @@ class FlutterStudioProjectOpenProcessor : FlutterProjectOpenProcessor() { return newProject } - writeAction { - for (module in FlutterModuleUtils.getModules(newProject)) { - if (FlutterModuleUtils.declaresFlutter(module) && !FlutterModuleUtils.isFlutterModule(module)) { + for (module in FlutterModuleUtils.getModules(newProject)) { + if (FlutterModuleUtils.declaresFlutter(module) && !FlutterModuleUtils.isFlutterModule(module)) { + writeAction { FlutterModuleUtils.setFlutterModuleType(module) - FlutterModuleUtils.enableDartSDK(module) } + FlutterModuleUtils.enableDartSDK(module) } - newProject.save() - EditorNotifications.getInstance(newProject).updateAllNotifications() } + newProject.save() + EditorNotifications.getInstance(newProject).updateAllNotifications() return newProject } diff --git a/src/io/flutter/project/FlutterProjectOpenProcessor.kt b/src/io/flutter/project/FlutterProjectOpenProcessor.kt index dab85dac64..004d137b4a 100644 --- a/src/io/flutter/project/FlutterProjectOpenProcessor.kt +++ b/src/io/flutter/project/FlutterProjectOpenProcessor.kt @@ -67,9 +67,7 @@ open class FlutterProjectOpenProcessor : ProjectOpenProcessor() { if (project == null || project.isDisposed) return project // Convert any modules that use Flutter but don't have IntelliJ Flutter metadata. - writeAction { - convertToFlutterProject(project) - } + convertToFlutterProject(project) return project } diff --git a/src/io/flutter/utils/FlutterModuleUtils.java b/src/io/flutter/utils/FlutterModuleUtils.java index 0ca8ae764e..3fc281e013 100644 --- a/src/io/flutter/utils/FlutterModuleUtils.java +++ b/src/io/flutter/utils/FlutterModuleUtils.java @@ -375,13 +375,14 @@ public static void setFlutterModuleType(@NotNull Module module) { public static void setFlutterModuleAndReload(@NotNull Module module, @NotNull Project project) { if (project.isDisposed()) return; + ApplicationManager.getApplication().invokeLater(() -> { + ApplicationManager.getApplication().runWriteAction(() -> setFlutterModuleType(module)); + enableDartSDK(module); + project.save(); - setFlutterModuleType(module); - enableDartSDK(module); - project.save(); - - EditorNotifications.getInstance(project).updateAllNotifications(); - ProjectManager.getInstance().reloadProject(project); + EditorNotifications.getInstance(project).updateAllNotifications(); + ProjectManager.getInstance().reloadProject(project); + }); } public static void enableDartSDK(final @NotNull Module module) {