diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/FunctionWarmupStartupActivity.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/FunctionWarmupStartupActivity.kt index 4833c54275..9f97782a3c 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/FunctionWarmupStartupActivity.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/FunctionWarmupStartupActivity.kt @@ -8,10 +8,8 @@ import com.intellij.openapi.application.EDT import com.intellij.openapi.project.Project import com.intellij.openapi.rd.util.lifetime import com.intellij.openapi.startup.ProjectActivity -import com.intellij.util.application import com.jetbrains.rd.util.reactive.adviseOnce import com.jetbrains.rider.run.configurations.runnableProjectsModelIfAvailable -import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.FunctionLaunchProfilesService import com.microsoft.azure.toolkit.intellij.legacy.function.localsettings.FunctionLocalSettingsService import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -20,9 +18,6 @@ class FunctionWarmupStartupActivity : ProjectActivity { override suspend fun execute(project: Project) { withContext(Dispatchers.EDT) { project.runnableProjectsModelIfAvailable?.projects?.adviseOnce(project.lifetime) { - application.runReadAction { - FunctionLaunchProfilesService.getInstance(project).initialize(it) - } FunctionLocalSettingsService.getInstance(project).initialize(it) } } diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/daemon/FunctionAppSolutionExtListener.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/daemon/FunctionAppSolutionExtListener.kt index 45c8d60685..7e70d5977a 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/daemon/FunctionAppSolutionExtListener.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/daemon/FunctionAppSolutionExtListener.kt @@ -14,6 +14,8 @@ import com.intellij.execution.configurations.ConfigurationTypeUtil import com.intellij.execution.executors.DefaultDebugExecutor import com.intellij.execution.executors.DefaultRunExecutor import com.intellij.openapi.actionSystem.ActionPlaces +import com.intellij.openapi.actionSystem.ActionUiKind +import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.ex.ActionUtil import com.intellij.openapi.actionSystem.impl.SimpleDataContext import com.intellij.openapi.client.ClientProjectSession @@ -21,21 +23,22 @@ import com.intellij.openapi.project.Project import com.jetbrains.rd.protocol.SolutionExtListener import com.jetbrains.rd.ui.bedsl.extensions.valueOrEmpty import com.jetbrains.rd.util.lifetime.Lifetime +import com.jetbrains.rd.util.threading.coroutines.adviseSuspend import com.jetbrains.rider.azure.model.FunctionAppDaemonModel import com.jetbrains.rider.model.RunnableProject import com.jetbrains.rider.model.runnableProjectsModel import com.jetbrains.rider.projectView.solution +import com.jetbrains.rider.run.configurations.launchSettings.LaunchSettingsJsonService import com.microsoft.azure.toolkit.intellij.legacy.function.actions.TriggerAzureFunctionAction -import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.* -import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.getApplicationUrl -import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.getArguments -import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.getEnvironmentVariables -import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.getWorkingDirectory -import com.microsoft.azure.toolkit.intellij.legacy.function.runner.localRun.* +import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.getFirstOrNullLaunchProfileProfileSuspend +import com.microsoft.azure.toolkit.intellij.legacy.function.runner.localRun.FunctionRunConfiguration +import com.microsoft.azure.toolkit.intellij.legacy.function.runner.localRun.FunctionRunConfigurationFactory +import com.microsoft.azure.toolkit.intellij.legacy.function.runner.localRun.FunctionRunConfigurationType +import kotlinx.coroutines.Dispatchers class FunctionAppSolutionExtListener : SolutionExtListener { override fun extensionCreated(lifetime: Lifetime, session: ClientProjectSession, model: FunctionAppDaemonModel) { - model.runFunctionApp.advise(lifetime) { + model.runFunctionApp.adviseSuspend(lifetime, Dispatchers.Main) { runConfiguration( functionName = it.functionName, runnableProject = getRunnableProject(session.project, it.projectFilePath), @@ -43,7 +46,7 @@ class FunctionAppSolutionExtListener : SolutionExtListener>>() - - fun initialize(runnableProjects: List) { - runnableProjects.forEach { - val launchSettingsFile = LaunchSettingsJsonService.getLaunchSettingsFileForProject(it) - ?: return@forEach - val profiles = getLaunchProfiles(launchSettingsFile) - cache[launchSettingsFile.absolutePath] = Pair(launchSettingsFile.lastModified(), profiles) - } - } - - fun getLaunchProfiles(runnableProject: RunnableProject): List { - val launchSettingsFile = LaunchSettingsJsonService.getLaunchSettingsFileForProject(runnableProject) - ?: return emptyList() - - val launchSettingsFileStamp = launchSettingsFile.lastModified() - val existingLaunchProfile = cache[launchSettingsFile.absolutePath] - if (existingLaunchProfile == null || launchSettingsFileStamp != existingLaunchProfile.first || existingLaunchProfile.second.isEmpty()) { - val profiles = getLaunchProfiles(launchSettingsFile) - cache[launchSettingsFile.absolutePath] = Pair(launchSettingsFileStamp, profiles) - return profiles - } - - return existingLaunchProfile.second - } - - fun getLaunchProfileByName(runnableProject: RunnableProject, launchProfileName: String?): LaunchProfile? = - getLaunchProfiles(runnableProject).find { it.name == launchProfileName } - - private fun getLaunchProfiles(launchSettingsFile: File): List { - val launchSettings = launchSettingsService.loadLaunchSettings(launchSettingsFile) - ?: return emptyList() - - return launchSettings - .profiles - .orEmpty() - .asSequence() - .filter { it.value.commandName.equals("Project", true) } - .map { (name, content) -> LaunchProfile(name, content) } - .sortedBy { it.name } - .toList() - } -} \ No newline at end of file diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/launchProfiles/LaunchSettingsJsonServiceExtensions.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/launchProfiles/LaunchSettingsJsonServiceExtensions.kt new file mode 100644 index 0000000000..cd9262cad5 --- /dev/null +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/launchProfiles/LaunchSettingsJsonServiceExtensions.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2018-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license. + */ + +package com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles + +import com.jetbrains.rider.model.RunnableProject +import com.jetbrains.rider.run.configurations.controls.LaunchProfile +import com.jetbrains.rider.run.configurations.launchSettings.LaunchSettingsJsonService +import kotlin.collections.asSequence +import kotlin.collections.component1 +import kotlin.collections.component2 + +fun LaunchSettingsJsonService.getFirstOrNullLaunchProfileProfile(runnableProject: RunnableProject): LaunchProfile? { + val profiles = loadLaunchSettings(runnableProject)?.profiles ?: return null + + return profiles + .asSequence() + .filter { it.value.commandName.equals("Project", true) } + .firstOrNull() + ?.let { LaunchProfile(it.key, it.value) } +} + +suspend fun LaunchSettingsJsonService.getFirstOrNullLaunchProfileProfileSuspend(runnableProject: RunnableProject): LaunchProfile? { + val profiles = loadLaunchSettingsSuspend(runnableProject)?.profiles ?: return null + + return profiles + .asSequence() + .filter { it.value.commandName.equals("Project", true) } + .firstOrNull() + ?.let { LaunchProfile(it.key, it.value) } +} + +suspend fun LaunchSettingsJsonService.getProjectLaunchProfiles(runnableProject: RunnableProject): List { + val profiles = loadLaunchSettingsSuspend(runnableProject)?.profiles ?: return emptyList() + + return profiles + .asSequence() + .filter { it.value.commandName.equals("Project", true) } + .map { (name, content) -> LaunchProfile(name, content) } + .sortedBy { it.name } + .toList() +} + +suspend fun LaunchSettingsJsonService.getProjectLaunchProfileByName( + runnableProject: RunnableProject, + launchProfileName: String? +): LaunchProfile? { + val profiles = loadLaunchSettingsSuspend(runnableProject)?.profiles ?: return null + + return profiles + .asSequence() + .filter { it.value.commandName.equals("Project", true) } + .firstOrNull { it.key == launchProfileName } + ?.let { (name, content) -> LaunchProfile(name, content) } +} \ No newline at end of file diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunConfigurationParameters.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunConfigurationParameters.kt index 551b92bce2..ba02d29eb8 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunConfigurationParameters.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunConfigurationParameters.kt @@ -4,12 +4,21 @@ import com.intellij.execution.configuration.EnvironmentVariablesComponent import com.intellij.execution.configurations.RuntimeConfigurationError import com.intellij.openapi.project.Project import com.intellij.openapi.util.JDOMExternalizerUtil +import com.jetbrains.rd.util.reactive.hasTrueValue +import com.jetbrains.rider.model.ProjectOutput +import com.jetbrains.rider.model.RunnableProject import com.jetbrains.rider.model.runnableProjectsModel import com.jetbrains.rider.projectView.solution import com.jetbrains.rider.run.RiderRunBundle +import com.jetbrains.rider.run.configurations.controls.LaunchProfile import com.jetbrains.rider.run.configurations.project.DotNetProjectConfigurationParameters import com.jetbrains.rider.run.configurations.project.DotNetStartBrowserParameters import com.microsoft.azure.toolkit.intellij.legacy.function.daemon.AzureRunnableProjectKinds +import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.getApplicationUrl +import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.getArguments +import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.getEnvironmentVariables +import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.getWorkingDirectory +import com.microsoft.azure.toolkit.intellij.legacy.function.localsettings.FunctionLocalSettings import org.jdom.Element import java.io.File @@ -62,17 +71,19 @@ class FunctionRunConfigurationParameters( } fun validate() { - if (project.solution.isLoaded.valueOrNull != true) { - throw RuntimeConfigurationError("Solution is loading, please wait for a few seconds.") + if (!project.solution.isLoaded.hasTrueValue) { + throw RuntimeConfigurationError(DotNetProjectConfigurationParameters.SOLUTION_IS_LOADING) } - val runnableProjects = project.solution.runnableProjectsModel.projects.valueOrNull - if (project.solution.isLoaded.valueOrNull != true || runnableProjects == null) { - throw RuntimeConfigurationError(DotNetProjectConfigurationParameters.SOLUTION_IS_LOADING) + ?: throw RuntimeConfigurationError(DotNetProjectConfigurationParameters.SOLUTION_IS_LOADING) + + if (projectFilePath.isEmpty()) { + throw RuntimeConfigurationError(DotNetProjectConfigurationParameters.PROJECT_NOT_SPECIFIED) } val project = runnableProjects.singleOrNull { it.projectFilePath == projectFilePath && it.kind == AzureRunnableProjectKinds.AzureFunctions - } ?: throw RuntimeConfigurationError(DotNetProjectConfigurationParameters.PROJECT_NOT_SPECIFIED) + } ?: throw RuntimeConfigurationError(RiderRunBundle.message("selected.project.not.found")) + if (!trackWorkingDirectory) { val workingDirectoryFile = File(workingDirectory) if (!workingDirectoryFile.exists() || !workingDirectoryFile.isDirectory) @@ -83,6 +94,7 @@ class FunctionRunConfigurationParameters( ) ) } + if (!project.problems.isNullOrEmpty()) { throw RuntimeConfigurationError(project.problems) } @@ -141,4 +153,29 @@ class FunctionRunConfigurationParameters( trackUrl, startBrowserParameters.copy() ) + + fun setUpFromRunnableProject( + runnableProject: RunnableProject, + projectOutput: ProjectOutput?, + launchProfile: LaunchProfile?, + functionName: String? = null, + localFunctionSettings: FunctionLocalSettings? = null + ) { + projectFilePath = runnableProject.projectFilePath + projectTfm = projectOutput?.tfm?.presentableName ?: "" + profileName = launchProfile?.name ?: "" + functionNames = if (functionName.isNullOrEmpty()) "" else functionName + trackArguments = true + arguments = getArguments(launchProfile?.content, projectOutput) + trackWorkingDirectory = true + workingDirectory = getWorkingDirectory(launchProfile?.content, projectOutput) + trackEnvs = true + envs = getEnvironmentVariables(launchProfile?.content) + useExternalConsole = false + trackUrl = true + startBrowserParameters.apply { + url = getApplicationUrl(launchProfile?.content, projectOutput, localFunctionSettings) + startAfterLaunch = launchProfile?.content?.launchBrowser == true + } + } } \ No newline at end of file diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunConfigurationProducer.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunConfigurationProducer.kt index 4dad0731b3..4ed0607b11 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunConfigurationProducer.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunConfigurationProducer.kt @@ -16,12 +16,9 @@ import com.jetbrains.rider.model.runnableProjectsModel import com.jetbrains.rider.projectView.solution import com.jetbrains.rider.projectView.workspace.getFile import com.jetbrains.rider.run.configurations.getSelectedProject +import com.jetbrains.rider.run.configurations.launchSettings.LaunchSettingsJsonService import com.microsoft.azure.toolkit.intellij.legacy.function.daemon.AzureRunnableProjectKinds import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.* -import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.getApplicationUrl -import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.getArguments -import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.getEnvironmentVariables -import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.getWorkingDirectory class FunctionRunConfigurationProducer : LazyRunConfigurationProducer() { override fun getConfigurationFactory() = @@ -33,14 +30,14 @@ class FunctionRunConfigurationProducer : LazyRunConfigurationProducer ): Boolean { - val selectedProjectFilePathInvariant = context.getSelectedProject()?.getFile()?.systemIndependentPath + val selectedProjectFilePath = context.getSelectedProject()?.getFile()?.systemIndependentPath ?: return false val projects = context.project.solution.runnableProjectsModel.projects.valueOrNull ?: return false val runnableProject = projects.firstOrNull { it.kind == AzureRunnableProjectKinds.AzureFunctions && - FileUtil.toSystemIndependentName(it.projectFilePath) == selectedProjectFilePathInvariant + FileUtil.toSystemIndependentName(it.projectFilePath) == selectedProjectFilePath } ?: return false if (configuration.name.isEmpty()) { configuration.name = runnableProject.name } - val projectOutput = runnableProject.projectOutputs.firstOrNull() - val launchProfile = FunctionLaunchProfilesService - .getInstance(context.project) - .getLaunchProfiles(runnableProject) + val projectOutput = runnableProject + .projectOutputs .firstOrNull() + val profile = LaunchSettingsJsonService + .getInstance(context.project) + .getFirstOrNullLaunchProfileProfile(runnableProject) - configuration.parameters.apply { - projectFilePath = selectedProjectFilePathInvariant - projectTfm = projectOutput?.tfm?.presentableName ?: "" - profileName = launchProfile?.name ?: "" - functionNames = "" - trackArguments = true - arguments = getArguments(launchProfile?.content, projectOutput) - trackWorkingDirectory = true - workingDirectory = getWorkingDirectory(launchProfile?.content, projectOutput) - trackEnvs = true - envs = getEnvironmentVariables(launchProfile?.content) - useExternalConsole = false - trackUrl = true - startBrowserParameters.apply { - url = getApplicationUrl(launchProfile?.content, projectOutput, null) - startAfterLaunch = launchProfile?.content?.launchBrowser == true - } - } + configuration.parameters.setUpFromRunnableProject( + runnableProject, + projectOutput, + profile + ) return true } diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunConfigurationType.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunConfigurationType.kt index 85f62e3733..7494d3bb93 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunConfigurationType.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunConfigurationType.kt @@ -22,11 +22,7 @@ import com.jetbrains.rider.run.configurations.launchSettings.LaunchSettingsJsonS import com.microsoft.azure.toolkit.ide.common.icon.AzureIcons import com.microsoft.azure.toolkit.intellij.common.IntelliJAzureIcons import com.microsoft.azure.toolkit.intellij.legacy.function.daemon.AzureRunnableProjectKinds -import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.* -import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.getApplicationUrl -import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.getArguments -import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.getEnvironmentVariables -import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.getWorkingDirectory +import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.getProjectLaunchProfileByName import com.microsoft.azure.toolkit.intellij.legacy.function.localsettings.FunctionLocalSettings import com.microsoft.azure.toolkit.intellij.legacy.function.localsettings.FunctionLocalSettingsService @@ -58,14 +54,14 @@ class FunctionRunConfigurationType : ConfigurationTypeBase( val functionProjects = projects.filter { it.kind == AzureRunnableProjectKinds.AzureFunctions } if (functionProjects.isEmpty()) return emptyList() + val service = LaunchSettingsJsonService.getInstance(project) val result = mutableListOf>() - functionProjects.forEach { runnableProject -> - val launchSettingsJsonService = LaunchSettingsJsonService.getInstance(project) - val launchProfiles = launchSettingsJsonService.loadLaunchSettings(runnableProject)?.profiles - if (launchProfiles != null) { + for (runnableProject in functionProjects) { + val profiles = service.loadLaunchSettingsSuspend(runnableProject)?.profiles + if (profiles != null && !profiles.isEmpty()) { generateConfigurationForProfiles( - launchProfiles, + profiles, runnableProject, runManager, autoGeneratedRunConfigurationManager, @@ -88,31 +84,7 @@ class FunctionRunConfigurationType : ConfigurationTypeBase( return result.toList() } - private fun hasRunConfigurationEverBeenGenerated( - autoGeneratedRunConfigurationManager: AutoGeneratedRunConfigurationManager, - projectFilePath: String, - profileName: String = "Default" - ) = autoGeneratedRunConfigurationManager.hasRunConfigurationEverBeenGenerated( - projectFilePath, - mapOf( - "azureFunctionProfileName" to profileName, - ) - ) - - private fun markProjectAsAutoGenerated( - autoGeneratedRunConfigurationManager: AutoGeneratedRunConfigurationManager, - projectFilePath: String, - profileName: String = "Default" - ) { - autoGeneratedRunConfigurationManager.markProjectAsAutoGenerated( - projectFilePath, - mapOf( - "azureFunctionProfileName" to profileName, - ) - ) - } - - private fun generateConfigurationForProfiles( + private suspend fun generateConfigurationForProfiles( launchProfiles: Map, runnableProject: RunnableProject, runManager: RunManager, @@ -121,23 +93,23 @@ class FunctionRunConfigurationType : ConfigurationTypeBase( ): List { val configurations = mutableListOf() - launchProfiles.forEach { profile -> + for (profile in launchProfiles) { if (!profile.value.commandName.equals("Project", true)) - return@forEach + continue if (hasRunConfigurationEverBeenGenerated( autoGeneratedRunConfigurationManager, runnableProject.projectFilePath, profile.key ) - ) return@forEach + ) continue val configurationName = if (runnableProject.name == profile.key) profile.key else "${runnableProject.name}: ${profile.key}" if (runManager.hasConfigurationForNameAndTypeId(configurationName, ID)) - return@forEach + continue val configuration = generateConfigurationForProfile( configurationName, @@ -160,7 +132,7 @@ class FunctionRunConfigurationType : ConfigurationTypeBase( return configurations } - private fun generateConfigurationForProfile( + private suspend fun generateConfigurationForProfile( name: String, runnableProject: RunnableProject, profile: String, @@ -168,17 +140,17 @@ class FunctionRunConfigurationType : ConfigurationTypeBase( project: Project ): RunnerAndConfigurationSettings { val settings = runManager.createConfiguration(name, factory) - val launchProfile = FunctionLaunchProfilesService - .getInstance(project) - .getLaunchProfileByName(runnableProject, profile) val projectOutput = runnableProject.projectOutputs.firstOrNull() + val launchProfile = LaunchSettingsJsonService + .getInstance(project) + .getProjectLaunchProfileByName(runnableProject, profile) val localFunctionSettings = FunctionLocalSettingsService .getInstance(project) .getFunctionLocalSettings(runnableProject) (settings.configuration as? FunctionRunConfiguration)?.updateConfigurationParameters( runnableProject, - launchProfile, projectOutput, + launchProfile, localFunctionSettings ) @@ -205,8 +177,8 @@ class FunctionRunConfigurationType : ConfigurationTypeBase( .getFunctionLocalSettings(runnableProject) (settings.configuration as? FunctionRunConfiguration)?.updateConfigurationParameters( runnableProject, - null, projectOutput, + null, localFunctionSettings ) @@ -218,25 +190,38 @@ class FunctionRunConfigurationType : ConfigurationTypeBase( private fun FunctionRunConfiguration.updateConfigurationParameters( runnableProject: RunnableProject, - launchProfile: LaunchProfile?, projectOutput: ProjectOutput?, + launchProfile: LaunchProfile?, localFunctionSettings: FunctionLocalSettings? - ) = parameters.apply { - projectFilePath = runnableProject.projectFilePath - profileName = launchProfile?.name ?: "" - projectTfm = projectOutput?.tfm?.presentableName ?: "" - functionNames = "" - arguments = getArguments(launchProfile?.content, projectOutput) - trackArguments = true - workingDirectory = getWorkingDirectory(launchProfile?.content, projectOutput) - trackWorkingDirectory = true - envs = getEnvironmentVariables(launchProfile?.content) - trackEnvs = true - useExternalConsole = false - startBrowserParameters.apply { - url = getApplicationUrl(launchProfile?.content, projectOutput, localFunctionSettings) - startAfterLaunch = launchProfile?.content?.launchBrowser == true - } - trackUrl = true + ) = parameters.setUpFromRunnableProject( + runnableProject, + projectOutput, + launchProfile, + null, + localFunctionSettings + ) + + private fun hasRunConfigurationEverBeenGenerated( + autoGeneratedRunConfigurationManager: AutoGeneratedRunConfigurationManager, + projectFilePath: String, + profileName: String = "Default" + ) = autoGeneratedRunConfigurationManager.hasRunConfigurationEverBeenGenerated( + projectFilePath, + mapOf( + "azureFunctionProfileName" to profileName, + ) + ) + + private fun markProjectAsAutoGenerated( + autoGeneratedRunConfigurationManager: AutoGeneratedRunConfigurationManager, + projectFilePath: String, + profileName: String = "Default" + ) { + autoGeneratedRunConfigurationManager.markProjectAsAutoGenerated( + projectFilePath, + mapOf( + "azureFunctionProfileName" to profileName, + ) + ) } } \ No newline at end of file diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunConfigurationViewModel.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunConfigurationViewModel.kt index 9024d83544..ea7f60c779 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunConfigurationViewModel.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunConfigurationViewModel.kt @@ -4,9 +4,14 @@ package com.microsoft.azure.toolkit.intellij.legacy.function.runner.localRun +import com.intellij.openapi.application.EDT +import com.intellij.openapi.application.ModalityState +import com.intellij.openapi.application.asContextElement import com.intellij.openapi.project.Project import com.jetbrains.rd.util.lifetime.Lifetime -import com.jetbrains.rd.util.reactive.adviseOnce +import com.jetbrains.rd.util.lifetime.SequentialLifetimes +import com.jetbrains.rd.util.threading.coroutines.launch +import com.jetbrains.rd.util.threading.coroutines.nextNotNullValue import com.jetbrains.rider.model.ProjectOutput import com.jetbrains.rider.model.RunnableProject import com.jetbrains.rider.model.RunnableProjectsModel @@ -15,6 +20,7 @@ import com.jetbrains.rider.run.configurations.controls.* import com.jetbrains.rider.run.configurations.controls.startBrowser.BrowserSettings import com.jetbrains.rider.run.configurations.controls.startBrowser.BrowserSettingsEditor import com.jetbrains.rider.run.configurations.launchSettings.LaunchSettingsJson +import com.jetbrains.rider.run.configurations.launchSettings.LaunchSettingsJsonService import com.jetbrains.rider.run.configurations.project.DotNetStartBrowserParameters import com.microsoft.azure.toolkit.intellij.legacy.function.daemon.AzureRunnableProjectKinds import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.* @@ -25,11 +31,12 @@ import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.getWo import com.microsoft.azure.toolkit.intellij.legacy.function.localsettings.FunctionLocalSettings import com.microsoft.azure.toolkit.intellij.legacy.function.localsettings.FunctionLocalSettingsService import com.microsoft.azure.toolkit.intellij.legacy.function.localsettings.FunctionWorkerRuntime +import kotlinx.coroutines.Dispatchers import java.io.File class FunctionRunConfigurationViewModel( private val project: Project, - private val lifetime: Lifetime, + lifetime: Lifetime, private val runnableProjectsModel: RunnableProjectsModel?, val projectSelector: ProjectSelector, val tfmSelector: StringSelector, @@ -43,6 +50,7 @@ class FunctionRunConfigurationViewModel( val urlEditor: TextEditor, val dotNetBrowserSettingsEditor: BrowserSettingsEditor ) : RunConfigurationViewModelBase() { + override val controls: List = listOf( projectSelector, @@ -58,6 +66,9 @@ class FunctionRunConfigurationViewModel( dotNetBrowserSettingsEditor ) + private val currentEditSessionLifetimeSource = SequentialLifetimes(lifetime) + private var currentEditSessionLifetime = currentEditSessionLifetimeSource.next() + private var isLoaded = false var trackArguments = true @@ -71,34 +82,35 @@ class FunctionRunConfigurationViewModel( disable() if (runnableProjectsModel != null) { + val projectModelLifetimes = SequentialLifetimes(lifetime) projectSelector.bindTo( runnableProjectsModel, lifetime, { p -> p.kind == AzureRunnableProjectKinds.AzureFunctions }, ::enable, - ::handleProjectSelection + { + projectModelLifetimes.next().launch(Dispatchers.EDT + ModalityState.current().asContextElement()) { + handleProjectSelection(it) + } + } ) } - tfmSelector.string.advise(lifetime) { handleChangeTfmSelection() } - launchProfileSelector.profile.advise(lifetime) { handleProfileSelection() } + tfmSelector.string.advise(lifetime) { recalculateFields() } + launchProfileSelector.profile.advise(lifetime) { recalculateFields() } programParametersEditor.parametersString.advise(lifetime) { handleArgumentsChange() } workingDirectorySelector.path.advise(lifetime) { handleWorkingDirectoryChange() } environmentVariablesEditor.envs.advise(lifetime) { handleEnvValueChange() } urlEditor.text.advise(lifetime) { handleUrlValueChange() } } - private fun handleProjectSelection(runnableProject: RunnableProject) { + private suspend fun handleProjectSelection(runnableProject: RunnableProject) { if (!isLoaded) return reloadTfmSelector(runnableProject) reloadLaunchProfileSelector(runnableProject) readLocalSettingsForProject(runnableProject) - - val projectOutput = getSelectedProjectOutput() - val launchProfile = launchProfileSelector.profile.valueOrNull - - recalculateFields(projectOutput, launchProfile?.content) + recalculateFields() } private fun reloadTfmSelector(runnableProject: RunnableProject) { @@ -112,8 +124,12 @@ class FunctionRunConfigurationViewModel( } } - private fun reloadLaunchProfileSelector(runnableProject: RunnableProject) { - val launchProfiles = FunctionLaunchProfilesService.getInstance(project).getLaunchProfiles(runnableProject) + private suspend fun reloadLaunchProfileSelector(runnableProject: RunnableProject) { + launchProfileSelector.isLoading.set(true) + + val launchProfiles = LaunchSettingsJsonService + .getInstance(project) + .getProjectLaunchProfiles(runnableProject) launchProfileSelector.profileList.apply { clear() addAll(launchProfiles) @@ -121,6 +137,8 @@ class FunctionRunConfigurationViewModel( if (launchProfiles.any()) { launchProfileSelector.profile.set(launchProfiles.first()) } + + launchProfileSelector.isLoading.set(false) } private fun readLocalSettingsForProject(runnableProject: RunnableProject) { @@ -129,44 +147,32 @@ class FunctionRunConfigurationViewModel( .getFunctionLocalSettings(runnableProject) } - private fun handleChangeTfmSelection() { + private fun recalculateFields() { if (!isLoaded) return val projectOutput = getSelectedProjectOutput() - val launchProfile = launchProfileSelector.profile.valueOrNull + val profile = launchProfileSelector.profile.valueOrNull - recalculateFields(projectOutput, launchProfile?.content) - } - - private fun handleProfileSelection() { - if (!isLoaded) return - - val projectOutput = getSelectedProjectOutput() - val launchProfile = launchProfileSelector.profile.valueOrNull - - recalculateFields(projectOutput, launchProfile?.content) - } - - private fun recalculateFields(projectOutput: ProjectOutput?, profile: LaunchSettingsJson.Profile?) { if (trackWorkingDirectory) { - val workingDirectory = getWorkingDirectory(profile, projectOutput) + val workingDirectory = getWorkingDirectory(profile?.content, projectOutput) workingDirectorySelector.path.set(workingDirectory) workingDirectorySelector.defaultValue.set(workingDirectory) } if (trackArguments) { - val arguments = getArguments(profile, projectOutput) + val arguments = getArguments(profile?.content, projectOutput) programParametersEditor.parametersString.set(arguments) programParametersEditor.defaultValue.set(arguments) } if (trackEnvs) { - val envs = getEnvironmentVariables(profile).toSortedMap() + val envs = getEnvironmentVariables(profile?.content).toSortedMap() environmentVariablesEditor.envs.set(envs) } if (trackUrl) { - val applicationUrl = getApplicationUrl(profile, projectOutput, functionLocalSettings) + val applicationUrl = getApplicationUrl(profile?.content, projectOutput, functionLocalSettings) urlEditor.text.value = applicationUrl urlEditor.defaultValue.value = applicationUrl - dotNetBrowserSettingsEditor.settings.value = BrowserSettings(profile?.launchBrowser == true, false, null) + dotNetBrowserSettingsEditor.settings.value = + BrowserSettings(profile?.content?.launchBrowser == true, false, null) } } @@ -226,13 +232,20 @@ class FunctionRunConfigurationViewModel( dotNetStartBrowserParameters: DotNetStartBrowserParameters ) { isLoaded = false + currentEditSessionLifetime = currentEditSessionLifetimeSource.next() this.trackArguments = trackArguments this.trackWorkingDirectory = trackWorkingDirectory this.trackEnvs = trackEnvs this.trackUrl = trackUrl - runnableProjectsModel?.projects?.adviseOnce(lifetime) { projectList -> + currentEditSessionLifetime.launch(Dispatchers.EDT + ModalityState.current().asContextElement()) { + val projectList = runnableProjectsModel + ?.projects + ?.nextNotNullValue() + ?.filter { it.kind == AzureRunnableProjectKinds.AzureFunctions } + ?: return@launch + functionNamesEditor.text.value = functionNames functionNamesEditor.defaultValue.value = "" @@ -244,9 +257,7 @@ class FunctionRunConfigurationViewModel( ) ) - if (projectFilePath.isEmpty() || projectList.none { - it.projectFilePath == projectFilePath && it.kind == AzureRunnableProjectKinds.AzureFunctions - }) { + if (projectFilePath.isEmpty() || projectList.none { it.projectFilePath == projectFilePath }) { if (projectFilePath.isEmpty()) { addFirstFunctionProject(projectList) } else { @@ -274,7 +285,7 @@ class FunctionRunConfigurationViewModel( } } - private fun addFirstFunctionProject(projectList: List) { + private suspend fun addFirstFunctionProject(projectList: List) { val runnableProject = projectList.firstOrNull { it.kind == AzureRunnableProjectKinds.AzureFunctions } ?: return projectSelector.project.set(runnableProject) @@ -301,7 +312,7 @@ class FunctionRunConfigurationViewModel( projectSelector.project.set(fakeProject) } - private fun addSelectedFunctionProject( + private suspend fun addSelectedFunctionProject( projectList: List, projectFilePath: String, tfm: String, @@ -352,7 +363,7 @@ class FunctionRunConfigurationViewModel( launchProfileSelector.profile.set(fakeLaunchProfile) } - val selectedOutput = getSelectedProjectOutput() ?: return + val selectedOutput = getSelectedProjectOutput() val effectiveArguments = if (trackArguments) getArguments(selectedProfile?.content, selectedOutput) diff --git a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunExecutorFactory.kt b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunExecutorFactory.kt index 836447968e..f34d118598 100644 --- a/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunExecutorFactory.kt +++ b/PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/legacy/function/runner/localRun/FunctionRunExecutorFactory.kt @@ -32,6 +32,7 @@ import com.jetbrains.rider.azure.model.functionAppDaemonModel import com.jetbrains.rider.model.runnableProjectsModel import com.jetbrains.rider.projectView.solution import com.jetbrains.rider.run.configurations.AsyncExecutorFactory +import com.jetbrains.rider.run.configurations.launchSettings.LaunchSettingsJsonService import com.jetbrains.rider.run.configurations.project.DotNetStartBrowserParameters import com.jetbrains.rider.run.environment.ExecutableParameterProcessor import com.jetbrains.rider.run.environment.ExecutableRunParameters @@ -47,6 +48,7 @@ import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.* import com.microsoft.azure.toolkit.intellij.legacy.function.localsettings.FunctionLocalSettings import com.microsoft.azure.toolkit.intellij.legacy.function.localsettings.FunctionLocalSettingsService import com.microsoft.azure.toolkit.intellij.legacy.function.localsettings.FunctionWorkerRuntime +import com.microsoft.azure.toolkit.intellij.legacy.function.launchProfiles.getProjectLaunchProfileByName import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.io.File @@ -167,9 +169,9 @@ class FunctionRunExecutorFactory( .projectOutputs .singleOrNull { it.tfm?.presentableName == parameters.projectTfm } - val launchProfile = FunctionLaunchProfilesService + val launchProfile = LaunchSettingsJsonService .getInstance(project) - .getLaunchProfileByName(runnableProject, parameters.profileName) + .getProjectLaunchProfileByName(runnableProject, parameters.profileName) val effectiveArguments = if (parameters.trackArguments) getArguments(launchProfile?.content, projectOutput)