Skip to content

Commit

Permalink
Aspire host run configuration created
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaelldi committed Nov 24, 2023
1 parent 34138ed commit 685c813
Show file tree
Hide file tree
Showing 11 changed files with 380 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Collections.Generic;
using JetBrains.Application;
using JetBrains.ProjectModel;
using JetBrains.ProjectModel.Properties;

namespace AspirePlugin.RunnableProject;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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<AspireHostConfiguration>(type) {
override fun createTemplateConfiguration(project: Project) = AspireHostConfiguration(
project,
this,
"Aspire Host",
AspireHostConfigurationParameters(
project, "", true, DotNetStartBrowserParameters()
)
)
}
Original file line number Diff line number Diff line change
@@ -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
}
}
}
Original file line number Diff line number Diff line change
@@ -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<RunnableProject>,
autoGeneratedRunConfigurationManager: AutoGeneratedRunConfigurationManager,
runManager: RunManager
): List<Pair<RunnableProject, RunnerAndConfigurationSettings>> {
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
}
}
}
Original file line number Diff line number Diff line change
@@ -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<ControlBase> =
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
}
}
}
Original file line number Diff line number Diff line change
@@ -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,

Check warning on line 13 in src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostExecutorFactory.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unused symbol

Property "project" is never used
private val parameters: AspireHostConfigurationParameters

Check warning on line 14 in src/main/kotlin/com/github/rafaelldi/aspireplugin/run/AspireHostExecutorFactory.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unused symbol

Property "parameters" is never used
) : 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("")
}
}

This file was deleted.

Loading

0 comments on commit 685c813

Please sign in to comment.