Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 6 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
57 changes: 0 additions & 57 deletions src/io/flutter/editor/FlutterStudioProjectOpenProcessor.java

This file was deleted.

81 changes: 81 additions & 0 deletions src/io/flutter/editor/FlutterStudioProjectOpenProcessor.kt
Original file line number Diff line number Diff line change
@@ -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
}
}
97 changes: 0 additions & 97 deletions src/io/flutter/project/FlutterProjectOpenProcessor.java

This file was deleted.

116 changes: 116 additions & 0 deletions src/io/flutter/project/FlutterProjectOpenProcessor.kt
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
}
Loading