From 685c81325c274f8bdef703ccd9cd47eb04c7eeca Mon Sep 17 00:00:00 2001 From: Rival Abdrakhmanov Date: Fri, 24 Nov 2023 22:41:44 +0100 Subject: [PATCH] Aspire host run configuration created --- .../AspireProjectPropertyRequest.cs | 1 - .../AspireRunnableProjectProvider.cs | 1 - .../run/AspireHostConfiguration.kt | 31 +++++ .../run/AspireHostConfigurationFactory.kt | 17 +++ .../run/AspireHostConfigurationParameters.kt | 63 +++++++++ .../run/AspireHostConfigurationType.kt | 59 ++++++++ .../run/AspireHostConfigurationViewModel.kt | 129 ++++++++++++++++++ .../run/AspireHostExecutorFactory.kt | 25 ++++ .../AspireHostRunConfigurationGenerator.kt | 27 ---- .../run/AspireHostSettingsEditor.kt | 55 ++++++++ src/main/resources/META-INF/plugin.xml | 3 +- 11 files changed, 380 insertions(+), 31 deletions(-) create mode 100644 src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostConfiguration.kt create mode 100644 src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostConfigurationFactory.kt create mode 100644 src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostConfigurationParameters.kt create mode 100644 src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostConfigurationType.kt create mode 100644 src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostConfigurationViewModel.kt create mode 100644 src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostExecutorFactory.kt delete mode 100644 src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostRunConfigurationGenerator.kt create mode 100644 src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostSettingsEditor.kt diff --git a/src/dotnet/aspire-plugin/RunnableProject/AspireProjectPropertyRequest.cs b/src/dotnet/aspire-plugin/RunnableProject/AspireProjectPropertyRequest.cs index 45f802e0..08d86769 100644 --- a/src/dotnet/aspire-plugin/RunnableProject/AspireProjectPropertyRequest.cs +++ b/src/dotnet/aspire-plugin/RunnableProject/AspireProjectPropertyRequest.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using JetBrains.Application; -using JetBrains.ProjectModel; using JetBrains.ProjectModel.Properties; namespace AspirePlugin.RunnableProject; diff --git a/src/dotnet/aspire-plugin/RunnableProject/AspireRunnableProjectProvider.cs b/src/dotnet/aspire-plugin/RunnableProject/AspireRunnableProjectProvider.cs index afebbdfc..7435af35 100644 --- a/src/dotnet/aspire-plugin/RunnableProject/AspireRunnableProjectProvider.cs +++ b/src/dotnet/aspire-plugin/RunnableProject/AspireRunnableProjectProvider.cs @@ -13,7 +13,6 @@ public class AspireRunnableProjectProvider : IRunnableProjectProvider { public JetBrains.Rider.Model.RunnableProject? CreateRunnableProject(IProject project, string name, string fullName, IconModel? icon) { - System.Diagnostics.Debugger.Launch(); if (!project.IsDotNetCoreProject()) return null; var isAspireHost = project.GetUniqueRequestedProjectProperty(IsAspireHost); diff --git a/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostConfiguration.kt b/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostConfiguration.kt new file mode 100644 index 00000000..5c865ed4 --- /dev/null +++ b/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostConfiguration.kt @@ -0,0 +1,31 @@ +package com.github.rafaelldi.aspireplugin.run + +import com.intellij.execution.configurations.ConfigurationFactory +import com.intellij.openapi.project.Project +import com.jetbrains.rider.run.configurations.RiderAsyncRunConfiguration +import org.jdom.Element + +class AspireHostConfiguration( + project: Project, + factory: ConfigurationFactory, + name: String, + val parameters: AspireHostConfigurationParameters +) : RiderAsyncRunConfiguration( + name, + project, + factory, + { AspireHostSettingsEditor(it) }, + AspireHostExecutorFactory(project, parameters) +) { + override fun checkConfiguration() { + parameters.validate() + } + + override fun readExternal(element: Element) { + parameters.readExternal(element) + } + + override fun writeExternal(element: Element) { + parameters.writeExternal(element) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostConfigurationFactory.kt b/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostConfigurationFactory.kt new file mode 100644 index 00000000..5d3db753 --- /dev/null +++ b/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostConfigurationFactory.kt @@ -0,0 +1,17 @@ +package com.github.rafaelldi.aspireplugin.run + +import com.intellij.openapi.project.Project +import com.jetbrains.rider.run.configurations.DotNetConfigurationFactoryBase +import com.jetbrains.rider.run.configurations.project.DotNetStartBrowserParameters + +class AspireHostConfigurationFactory(type: AspireHostConfigurationType) : + DotNetConfigurationFactoryBase(type) { + override fun createTemplateConfiguration(project: Project) = AspireHostConfiguration( + project, + this, + "Aspire Host", + AspireHostConfigurationParameters( + project, "", true, DotNetStartBrowserParameters() + ) + ) +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostConfigurationParameters.kt b/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostConfigurationParameters.kt new file mode 100644 index 00000000..33cc0430 --- /dev/null +++ b/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostConfigurationParameters.kt @@ -0,0 +1,63 @@ +package com.github.rafaelldi.aspireplugin.run + +import com.intellij.execution.configurations.RuntimeConfigurationError +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.JDOMExternalizerUtil +import com.jetbrains.rider.model.RunnableProject +import com.jetbrains.rider.model.runnableProjectsModel +import com.jetbrains.rider.projectView.solution +import com.jetbrains.rider.run.configurations.project.DotNetProjectConfigurationParameters +import com.jetbrains.rider.run.configurations.project.DotNetStartBrowserParameters +import com.jetbrains.rider.run.configurations.project.getRunOptions +import org.jdom.Element + +class AspireHostConfigurationParameters( + private val project: Project, + var projectFilePath: String, + var trackUrl: Boolean, + var startBrowserParameters: DotNetStartBrowserParameters +) { + companion object { + private const val PROJECT_FILE_PATH = "PROJECT_FILE_PATH" + private const val TRACK_URL = "TRACK_URL" + } + + fun validate() { + val runnableProjects = project.solution.runnableProjectsModel.projects.valueOrNull + if (project.solution.isLoaded.valueOrNull != true || runnableProjects == null) { + throw RuntimeConfigurationError(DotNetProjectConfigurationParameters.SOLUTION_IS_LOADING) + } + val project = runnableProjects.singleOrNull { + it.projectFilePath == projectFilePath && AspireHostConfigurationType.isTypeApplicable(it.kind) + } ?: throw RuntimeConfigurationError(DotNetProjectConfigurationParameters.PROJECT_NOT_SPECIFIED) + if (!project.problems.isNullOrEmpty()) { + throw RuntimeConfigurationError(project.problems) + } + } + + fun readExternal(element: Element) { + projectFilePath = JDOMExternalizerUtil.readField(element, PROJECT_FILE_PATH) ?: "" + val trackUrlString = JDOMExternalizerUtil.readField(element, TRACK_URL) ?: "" + trackUrl = trackUrlString != "0" + startBrowserParameters = DotNetStartBrowserParameters.readExternal(element) + } + + fun writeExternal(element: Element) { + JDOMExternalizerUtil.writeField(element, PROJECT_FILE_PATH, projectFilePath) + JDOMExternalizerUtil.writeField(element, TRACK_URL, if (trackUrl) "1" else "0") + startBrowserParameters.writeExternal(element) + } +} + +fun AspireHostConfigurationParameters.setUpFromRunnableProject(project: RunnableProject) { + projectFilePath = project.projectFilePath + trackUrl = true + val runOptions = project.getRunOptions() + val startBrowserUrl = runOptions.startBrowserUrl + if (startBrowserUrl.isNotEmpty()) { + startBrowserParameters.apply { + url = startBrowserUrl + startAfterLaunch = runOptions.launchBrowser + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostConfigurationType.kt b/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostConfigurationType.kt new file mode 100644 index 00000000..bfc0f6b3 --- /dev/null +++ b/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostConfigurationType.kt @@ -0,0 +1,59 @@ +package com.github.rafaelldi.aspireplugin.run + +import com.intellij.execution.RunManager +import com.intellij.execution.RunnerAndConfigurationSettings +import com.intellij.execution.configurations.ConfigurationTypeBase +import com.intellij.openapi.project.Project +import com.jetbrains.rd.util.lifetime.Lifetime +import com.jetbrains.rider.model.RunnableProject +import com.jetbrains.rider.model.RunnableProjectKind +import com.jetbrains.rider.run.AutoGeneratedRunConfigurationManager +import com.jetbrains.rider.run.configurations.IRunConfigurationWithDefault +import com.jetbrains.rider.run.configurations.IRunnableProjectConfigurationType +import com.jetbrains.rider.run.configurations.RunConfigurationHelper.hasConfigurationForNameAndTypeId +import icons.RiderIcons + +class AspireHostConfigurationType : ConfigurationTypeBase( + "AspireHostConfiguration", + "Aspire Host", + "Aspire Host configuration", + RiderIcons.RunConfigurations.Application +), IRunnableProjectConfigurationType, IRunConfigurationWithDefault { + companion object { + fun isTypeApplicable(kind: RunnableProjectKind) = kind == AspireRunnableProjectKinds.AspireHost + } + + private val factory = AspireHostConfigurationFactory(this) + + init { + addFactory(factory) + } + + override fun isApplicable(kind: RunnableProjectKind) = isTypeApplicable(kind) + + override fun tryCreateDefault( + project: Project, + lifetime: Lifetime, + projects: List, + autoGeneratedRunConfigurationManager: AutoGeneratedRunConfigurationManager, + runManager: RunManager + ): List> { + val applicableProjects = projects.filter { + isApplicable(it.kind) + && !runManager.hasConfigurationForNameAndTypeId(it.name, this.id) + && !autoGeneratedRunConfigurationManager.hasRunConfigurationEverBeenGenerated( + it.projectFilePath, + it.kind + ) + } + + return applicableProjects.map { + val defaultSettings = runManager.createConfiguration(it.name, factory).apply { + (configuration as AspireHostConfiguration).parameters.setUpFromRunnableProject(it) + } + runManager.addConfiguration(defaultSettings) + autoGeneratedRunConfigurationManager.markProjectAsAutoGenerated(it.projectFilePath, it.kind) + it to defaultSettings + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostConfigurationViewModel.kt b/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostConfigurationViewModel.kt new file mode 100644 index 00000000..052b1670 --- /dev/null +++ b/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostConfigurationViewModel.kt @@ -0,0 +1,129 @@ +package com.github.rafaelldi.aspireplugin.run + +import com.jetbrains.rd.util.lifetime.Lifetime +import com.jetbrains.rd.util.reactive.adviseOnce +import com.jetbrains.rider.model.RunnableProject +import com.jetbrains.rider.model.RunnableProjectsModel +import com.jetbrains.rider.run.configurations.RunnableProjectKinds +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.project.DotNetStartBrowserParameters +import com.jetbrains.rider.run.configurations.project.getRunOptions +import java.io.File + +class AspireHostConfigurationViewModel( + private val lifetime: Lifetime, + private val runnableProjectsModel: RunnableProjectsModel?, + val projectSelector: ProjectSelector, + separator: ViewSeparator, + val urlEditor: TextEditor, + val dotNetBrowserSettingsEditor: BrowserSettingsEditor +) : RunConfigurationViewModelBase() { + override val controls: List = + listOf( + projectSelector, + separator, + urlEditor, + dotNetBrowserSettingsEditor + ) + + private var isLoaded = false + var trackUrl = true + + init { + disable() + + if (runnableProjectsModel != null) { + projectSelector.bindTo( + runnableProjectsModel, + lifetime, + { p -> AspireHostConfigurationType.isTypeApplicable(p.kind) }, + ::enable, + ::handleProjectSelection + ) + } + + urlEditor.text.advise(lifetime) { handleUrlValueChange() } + } + + private fun handleProjectSelection(project: RunnableProject) { + if (!isLoaded) { + return + } + + val runOptions = project.getRunOptions() + val startBrowserUrl = runOptions.startBrowserUrl + if (startBrowserUrl.isNotEmpty()) { + urlEditor.defaultValue.value = startBrowserUrl + urlEditor.text.value = startBrowserUrl + dotNetBrowserSettingsEditor.settings.value = BrowserSettings(runOptions.launchBrowser, false, null) + } + } + + private fun handleUrlValueChange() { + projectSelector.project.valueOrNull?.let { + val runOptions = it.getRunOptions() + trackUrl = urlEditor.text.value == runOptions.startBrowserUrl + } + } + + fun reset(projectFilePath: String, trackUrl: Boolean, dotNetStartBrowserParameters: DotNetStartBrowserParameters) { + isLoaded = false + + this.trackUrl = trackUrl + + runnableProjectsModel?.projects?.adviseOnce(lifetime) { projectList -> + dotNetBrowserSettingsEditor.settings.set( + BrowserSettings( + dotNetStartBrowserParameters.startAfterLaunch, + dotNetStartBrowserParameters.withJavaScriptDebugger, + dotNetStartBrowserParameters.browser + ) + ) + + if (projectFilePath.isEmpty() || projectList.none { + it.projectFilePath == projectFilePath && AspireHostConfigurationType.isTypeApplicable(it.kind) + }) { + if (projectFilePath.isEmpty()) { + projectList.firstOrNull { AspireHostConfigurationType.isTypeApplicable(it.kind) } + ?.let { project -> + projectSelector.project.set(project) + isLoaded = true + handleProjectSelection(project) + } + } else { + val fakeProjectName = File(projectFilePath).name + val fakeProject = RunnableProject( + fakeProjectName, + fakeProjectName, + projectFilePath, + RunnableProjectKinds.Unloaded, + emptyList(), + emptyList(), + null, + emptyList() + ) + projectSelector.projectList.apply { + clear() + addAll(projectList + fakeProject) + } + projectSelector.project.set(fakeProject) + } + } else { + projectList.singleOrNull { + it.projectFilePath == projectFilePath && AspireHostConfigurationType.isTypeApplicable(it.kind) + }?.let { project -> + projectSelector.project.set(project) + + val runOptions = project.getRunOptions() + val effectiveUrl = if (trackUrl) runOptions.startBrowserUrl else dotNetStartBrowserParameters.url + urlEditor.defaultValue.value = effectiveUrl + urlEditor.text.value = effectiveUrl + } + } + + isLoaded = true + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostExecutorFactory.kt b/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostExecutorFactory.kt new file mode 100644 index 00000000..107e8d13 --- /dev/null +++ b/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostExecutorFactory.kt @@ -0,0 +1,25 @@ +package com.github.rafaelldi.aspireplugin.run + +import com.intellij.execution.CantRunException +import com.intellij.execution.configurations.RunProfileState +import com.intellij.execution.executors.DefaultDebugExecutor +import com.intellij.execution.executors.DefaultRunExecutor +import com.intellij.execution.runners.ExecutionEnvironment +import com.intellij.openapi.project.Project +import com.jetbrains.rd.util.lifetime.Lifetime +import com.jetbrains.rider.run.configurations.AsyncExecutorFactory + +class AspireHostExecutorFactory( + private val project: Project, + private val parameters: AspireHostConfigurationParameters +) : AsyncExecutorFactory { + override suspend fun create( + executorId: String, + environment: ExecutionEnvironment, + lifetime: Lifetime + ): RunProfileState = when (executorId) { + DefaultDebugExecutor.EXECUTOR_ID -> throw CantRunException("") + DefaultRunExecutor.EXECUTOR_ID -> throw CantRunException("") + else -> throw CantRunException("") + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostRunConfigurationGenerator.kt b/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostRunConfigurationGenerator.kt deleted file mode 100644 index 7bb17158..00000000 --- a/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostRunConfigurationGenerator.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.github.rafaelldi.aspireplugin.run - -import com.intellij.execution.RunManager -import com.intellij.execution.RunnerAndConfigurationSettings -import com.intellij.openapi.diagnostic.logger -import com.intellij.openapi.project.Project -import com.jetbrains.rider.model.RunnableProject -import com.jetbrains.rider.run.AutoGeneratedRunConfigurationManager -import com.jetbrains.rider.run.ExternalRunConfigurationGeneratorExtension - -class AspireHostRunConfigurationGenerator(private val project: Project) : ExternalRunConfigurationGeneratorExtension { - companion object { - private val LOG = logger() - } - - override fun generateConfigurations( - runnableProjects: List, - autoGeneratedRunConfigurationManager: AutoGeneratedRunConfigurationManager, - runManager: RunManager - ): List> { - val applicableProjects = runnableProjects.filter { - it.kind == AspireRunnableProjectKinds.AspireHost - } - - return emptyList() - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostSettingsEditor.kt b/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostSettingsEditor.kt new file mode 100644 index 00000000..d76b6d13 --- /dev/null +++ b/src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostSettingsEditor.kt @@ -0,0 +1,55 @@ +package com.github.rafaelldi.aspireplugin.run + +import com.intellij.openapi.project.Project +import com.jetbrains.rd.util.lifetime.Lifetime +import com.jetbrains.rider.run.configurations.ProtocolLifetimedSettingsEditor +import com.jetbrains.rider.run.configurations.controls.ControlViewBuilder +import com.jetbrains.rider.run.configurations.controls.ProjectSelector +import com.jetbrains.rider.run.configurations.controls.TextEditor +import com.jetbrains.rider.run.configurations.controls.ViewSeparator +import com.jetbrains.rider.run.configurations.controls.startBrowser.BrowserSettingsEditor +import com.jetbrains.rider.run.configurations.runnableProjectsModelIfAvailable +import javax.swing.JComponent + +class AspireHostSettingsEditor(private val project: Project) : + ProtocolLifetimedSettingsEditor() { + private lateinit var viewModel: AspireHostConfigurationViewModel + + override fun createEditor(lifetime: Lifetime): JComponent { + viewModel = AspireHostConfigurationViewModel( + lifetime, + project.runnableProjectsModelIfAvailable, + ProjectSelector("Project:", "Project"), + ViewSeparator("Open browser"), + TextEditor("URL", "URL", lifetime), + BrowserSettingsEditor("") + ) + return ControlViewBuilder(lifetime, project, "AspireHost").build(viewModel) + } + + override fun applyEditorTo(configuration: AspireHostConfiguration) { + val selectedProject = viewModel.projectSelector.project.valueOrNull + if (selectedProject != null) { + configuration.parameters.apply { + projectFilePath = selectedProject.projectFilePath + trackUrl = viewModel.trackUrl + startBrowserParameters.url = viewModel.urlEditor.text.value + startBrowserParameters.browser = viewModel.dotNetBrowserSettingsEditor.settings.value.myBrowser + startBrowserParameters.startAfterLaunch = + viewModel.dotNetBrowserSettingsEditor.settings.value.startAfterLaunch + startBrowserParameters.withJavaScriptDebugger = + viewModel.dotNetBrowserSettingsEditor.settings.value.withJavaScriptDebugger + } + } + } + + override fun resetEditorFrom(configuration: AspireHostConfiguration) { + configuration.parameters.apply { + viewModel.reset( + projectFilePath, + trackUrl, + startBrowserParameters + ) + } + } +} \ No newline at end of file diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 73e52f05..e3d667d8 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -10,8 +10,7 @@ messages.AspireBundle - +