diff --git a/resharper/src/UnitTesting/GodotTestRunnerHost.cs b/resharper/src/UnitTesting/GodotTestRunnerHost.cs index e4121e61a..297fd6536 100644 --- a/resharper/src/UnitTesting/GodotTestRunnerHost.cs +++ b/resharper/src/UnitTesting/GodotTestRunnerHost.cs @@ -54,7 +54,9 @@ public override IPreparedProcess StartProcess(ProcessStartInfo startInfo, ITestR var patcher = new GodotPatcher(solution, scenePaths.Single().MakeRelativeTo(solutionDirectory)); var request = context.RuntimeEnvironment.ToJetProcessRuntimeRequest(); var patch = new JetProcessStartInfoPatch(patcher, request); - return new PreparedProcess(rawStartInfo, patch); + var preparedProcess = new PreparedProcess(rawStartInfo, patch); + CaptureOutputIfRequired(preparedProcess, context); + return preparedProcess; } public override IEnumerable InProcessAssemblies => EmptyArray.Instance; @@ -93,6 +95,26 @@ private Task PrepareDebuggerServer(IUnitTestRun run) return tcs.Task; } + + private void CaptureOutputIfRequired(IPreparedProcess process, ITestRunnerContext context) + { + // in debug redirect output to + if (context is ITestRunnerExecutionContext executionContext && + executionContext.Run.HostController.HostId == WellKnownHostProvidersIds.DebugProviderId) + { + var solution = context.RuntimeEnvironment.Project.GetSolution(); + process.ErrorLineRead += line => { Send(solution, context.Lifetime, myDebugPort, line, TestRunnerOutputEventType.Error); }; + process.OutputLineRead += line => { Send(solution, context.Lifetime, myDebugPort, line, TestRunnerOutputEventType.Message); }; + } + } + + private static void Send(ISolution solution, Lifetime lifetime, int port, string line, + TestRunnerOutputEventType gameOutputEventType) + { + if (line == null) return; + var model = solution.GetProtocolSolution().GetGodotFrontendBackendModel(); + solution.Locks.ExecuteOrQueueEx(lifetime, "GameOutputEvent", () => model.OnTestRunnerOutputEvent(new TestRunnerOutputEvent(port, gameOutputEventType, line))); + } private class GodotPatcher : IProcessStartInfoPatcher { diff --git a/rider/protocol/src/kotlin/model/frontendBackend/GodotFrontendBackendModel.kt b/rider/protocol/src/kotlin/model/frontendBackend/GodotFrontendBackendModel.kt index d88c961d5..a596b3c9f 100644 --- a/rider/protocol/src/kotlin/model/frontendBackend/GodotFrontendBackendModel.kt +++ b/rider/protocol/src/kotlin/model/frontendBackend/GodotFrontendBackendModel.kt @@ -9,6 +9,15 @@ import com.jetbrains.rd.generator.nova.kotlin.Kotlin11Generator @Suppress("unused") object GodotFrontendBackendModel : Ext(SolutionModel.Solution) { + val TestRunnerOutputEvent = structdef("TestRunnerOutputEvent"){ + field("port", int) + field("type", enum("TestRunnerOutputEventType") { + +"Error" + +"Message" + }) + field("message", string) + } + init { setting(Kotlin11Generator.Namespace, "com.jetbrains.rider.model.godot.frontendBackend") setting(CSharp50Generator.Namespace, "JetBrains.Rider.Model.Godot.FrontendBackend") @@ -16,6 +25,7 @@ object GodotFrontendBackendModel : Ext(SolutionModel.Solution) { // Actions called from the backend to the frontend sink("activateRider", void).documentation = "Tell Rider to bring itself to the foreground. Called when opening a file from Godot" callback("startDebuggerServer", void, int).documentation = "Tell the frontend to start listening for debugger. Returns port. Used for debugging unit tests" + sink("onTestRunnerOutputEvent", TestRunnerOutputEvent).documentation = "Pass output of the game under tests to frontend" // Misc backend/fronted context property("godotPath", string).documentation = "Path to GodotEditor" diff --git a/rider/src/main/kotlin/com/jetbrains/rider/plugins/godot/FrontendBackendHost.kt b/rider/src/main/kotlin/com/jetbrains/rider/plugins/godot/FrontendBackendHost.kt index ca16ae5b9..90550d2b4 100644 --- a/rider/src/main/kotlin/com/jetbrains/rider/plugins/godot/FrontendBackendHost.kt +++ b/rider/src/main/kotlin/com/jetbrains/rider/plugins/godot/FrontendBackendHost.kt @@ -10,20 +10,29 @@ import com.intellij.openapi.wm.WindowManager import com.intellij.util.BitUtil import com.jetbrains.rd.framework.impl.RdTask import com.jetbrains.rd.platform.util.idea.ProtocolSubscribedProjectComponent -import com.jetbrains.rd.util.reactive.AddRemove -import com.jetbrains.rd.util.reactive.adviseNotNull +import com.jetbrains.rd.util.firstOrNull +import com.jetbrains.rd.util.lifetime.onTermination +import com.jetbrains.rd.util.reactive.* import com.jetbrains.rider.debugger.DebuggerInitializingState +import com.jetbrains.rider.debugger.DotNetDebugProcess import com.jetbrains.rider.debugger.RiderDebugActiveDotNetSessionsTracker +import com.jetbrains.rider.debugger.tryWriteMessageToConsoleView +import com.jetbrains.rider.model.debuggerWorker.OutputMessageWithSubject +import com.jetbrains.rider.model.debuggerWorker.OutputSubject +import com.jetbrains.rider.model.debuggerWorker.OutputType +import com.jetbrains.rider.model.godot.frontendBackend.TestRunnerOutputEventType import com.jetbrains.rider.model.godot.frontendBackend.godotFrontendBackendModel import com.jetbrains.rider.plugins.godot.run.GodotRunConfigurationGenerator +import com.jetbrains.rider.plugins.godot.run.configurations.GodotDotNetRemoteConfiguration +import com.jetbrains.rider.plugins.godot.run.configurations.GodotDotNetRemoteConfigurationFactory import com.jetbrains.rider.projectView.solution -import com.jetbrains.rider.run.configurations.remote.DotNetRemoteConfiguration import com.jetbrains.rider.run.configurations.remote.MonoRemoteConfigType import com.jetbrains.rider.util.NetUtils import java.awt.Frame class FrontendBackendHost(project: Project) : ProtocolSubscribedProjectComponent(project) { - val model = project.solution.godotFrontendBackendModel + private val model = project.solution.godotFrontendBackendModel + private val debugProcesses = mutableMapOf() init { model.activateRider.advise(projectComponentLifetime) { @@ -35,23 +44,38 @@ class FrontendBackendHost(project: Project) : ProtocolSubscribedProjectComponent } } + model.onTestRunnerOutputEvent.advise(projectComponentLifetime) { output-> + debugProcesses.filter{it.key == output.port}.firstOrNull()?.value?.console?.tryWriteMessageToConsoleView( + OutputMessageWithSubject( + output = "${output.message}\r\n", + type = when (output.type) { + TestRunnerOutputEventType.Message -> OutputType.Info + TestRunnerOutputEventType.Error -> OutputType.Error + }, + subject = OutputSubject.Default + ) + ) + } + model.startDebuggerServer.set { lt, _ -> val task = RdTask() val runManager = RunManager.getInstance(project) val configurationType = ConfigurationTypeUtil.findConfigurationType(MonoRemoteConfigType::class.java) val runConfiguration = runManager.createConfiguration( GodotRunConfigurationGenerator.ATTACH_CONFIGURATION_NAME, - configurationType.factory + GodotDotNetRemoteConfigurationFactory(configurationType) ) - val remoteConfiguration = runConfiguration.configuration as DotNetRemoteConfiguration + val remoteConfiguration = runConfiguration.configuration as GodotDotNetRemoteConfiguration remoteConfiguration.listenPortForConnections = true remoteConfiguration.port = NetUtils.findFreePort(500013) remoteConfiguration.address = "127.0.0.1" val processTracker: RiderDebugActiveDotNetSessionsTracker = RiderDebugActiveDotNetSessionsTracker.getInstance(project) - processTracker.dotNetDebugProcesses.change.advise(projectComponentLifetime) { (event, debugProcess) -> + processTracker.dotNetDebugProcesses.change.advise(lt) { (event, debugProcess) -> if (event == AddRemove.Add) { + debugProcesses[remoteConfiguration.port] = debugProcess + debugProcess.sessionLifetime.onTermination { debugProcesses.remove(remoteConfiguration.port) } debugProcess.initializeDebuggerTask.debuggerInitializingState.advise(lt) { if (it == DebuggerInitializingState.Initialized) task.set(remoteConfiguration.port) diff --git a/rider/src/main/kotlin/com/jetbrains/rider/plugins/godot/run/configurations/GodotDebugProfileState.kt b/rider/src/main/kotlin/com/jetbrains/rider/plugins/godot/run/configurations/GodotDebugProfileState.kt index d41e92506..d4dede85d 100644 --- a/rider/src/main/kotlin/com/jetbrains/rider/plugins/godot/run/configurations/GodotDebugProfileState.kt +++ b/rider/src/main/kotlin/com/jetbrains/rider/plugins/godot/run/configurations/GodotDebugProfileState.kt @@ -20,21 +20,20 @@ import com.jetbrains.rider.model.debuggerWorker.DebuggerWorkerModel import com.jetbrains.rider.model.debuggerWorker.OutputMessageWithSubject import com.jetbrains.rider.model.debuggerWorker.OutputSubject import com.jetbrains.rider.model.debuggerWorker.OutputType - import com.jetbrains.rider.model.godot.frontendBackend.godotFrontendBackendModel import com.jetbrains.rider.plugins.godot.model.debuggerWorker.godotDebuggerWorkerModel import com.jetbrains.rider.projectView.solution -import com.jetbrains.rider.run.* -import com.jetbrains.rider.run.configurations.remote.MonoConnectRemoteProfileState +import com.jetbrains.rider.run.ExternalConsoleMediator +import com.jetbrains.rider.run.WorkerRunInfo import com.jetbrains.rider.run.configurations.remote.RemoteConfiguration +import com.jetbrains.rider.run.createEmptyConsoleCommandLine +import com.jetbrains.rider.run.withRawParameters import com.jetbrains.rider.util.NetUtils class GodotDebugProfileState(private val exeConfiguration: GodotDebugRunConfiguration, private val remoteConfiguration: RemoteConfiguration, executionEnvironment: ExecutionEnvironment) - : MonoConnectRemoteProfileState(remoteConfiguration, executionEnvironment) { + : GodotMonoConnectRemoteProfileState(remoteConfiguration, executionEnvironment) { private val ansiEscapeDecoder = AnsiEscapeDecoder() - override val consoleKind: ConsoleKind = ConsoleKind.Normal - override suspend fun createDebuggerWorker( workerCmd: GeneralCommandLine, protocolModel: DebuggerWorkerModel, diff --git a/rider/src/main/kotlin/com/jetbrains/rider/plugins/godot/run/configurations/GodotDotNetRemoteConfiguration.kt b/rider/src/main/kotlin/com/jetbrains/rider/plugins/godot/run/configurations/GodotDotNetRemoteConfiguration.kt new file mode 100644 index 000000000..cbc5a3323 --- /dev/null +++ b/rider/src/main/kotlin/com/jetbrains/rider/plugins/godot/run/configurations/GodotDotNetRemoteConfiguration.kt @@ -0,0 +1,17 @@ +package com.jetbrains.rider.plugins.godot.run.configurations + +import com.intellij.execution.Executor +import com.intellij.execution.configurations.ConfigurationFactory +import com.intellij.execution.configurations.RunProfileState +import com.intellij.execution.executors.DefaultDebugExecutor +import com.intellij.execution.runners.ExecutionEnvironment +import com.intellij.openapi.project.Project +import com.jetbrains.rider.run.configurations.remote.DotNetRemoteConfiguration + +class GodotDotNetRemoteConfiguration(project: Project, factory: ConfigurationFactory, name:String): DotNetRemoteConfiguration(project, factory, name) { + override fun getState(executor: Executor, environment: ExecutionEnvironment): RunProfileState? { + if (executor.id != DefaultDebugExecutor.EXECUTOR_ID) + return null + return GodotMonoConnectRemoteProfileState(this, environment) + } +} \ No newline at end of file diff --git a/rider/src/main/kotlin/com/jetbrains/rider/plugins/godot/run/configurations/GodotDotNetRemoteConfigurationFactory.kt b/rider/src/main/kotlin/com/jetbrains/rider/plugins/godot/run/configurations/GodotDotNetRemoteConfigurationFactory.kt new file mode 100644 index 000000000..9cb231d17 --- /dev/null +++ b/rider/src/main/kotlin/com/jetbrains/rider/plugins/godot/run/configurations/GodotDotNetRemoteConfigurationFactory.kt @@ -0,0 +1,16 @@ +package com.jetbrains.rider.plugins.godot.run.configurations + +import com.intellij.execution.configurations.RunConfiguration +import com.intellij.openapi.project.Project +import com.jetbrains.rider.run.configurations.DotNetConfigurationFactoryBase +import com.jetbrains.rider.run.configurations.remote.DotNetRemoteConfiguration +import com.jetbrains.rider.run.configurations.remote.MonoRemoteConfigType + +class GodotDotNetRemoteConfigurationFactory(monoRemoteConfigType: MonoRemoteConfigType) : DotNetConfigurationFactoryBase(monoRemoteConfigType){ + override fun getId(): String { + // super.getId() does the same, but prints a deprecation message + return name + } + + override fun createTemplateConfiguration(project: Project): RunConfiguration = GodotDotNetRemoteConfiguration(project, this, "") +} \ No newline at end of file diff --git a/rider/src/main/kotlin/com/jetbrains/rider/plugins/godot/run/configurations/GodotMonoRemoteConfiguration.kt b/rider/src/main/kotlin/com/jetbrains/rider/plugins/godot/run/configurations/GodotMonoRemoteConfiguration.kt new file mode 100644 index 000000000..44ee3d34e --- /dev/null +++ b/rider/src/main/kotlin/com/jetbrains/rider/plugins/godot/run/configurations/GodotMonoRemoteConfiguration.kt @@ -0,0 +1,10 @@ +package com.jetbrains.rider.plugins.godot.run.configurations + +import com.intellij.execution.runners.ExecutionEnvironment +import com.jetbrains.rider.run.ConsoleKind +import com.jetbrains.rider.run.configurations.remote.MonoConnectRemoteProfileState +import com.jetbrains.rider.run.configurations.remote.RemoteConfiguration + +open class GodotMonoConnectRemoteProfileState(remoteConfiguration: RemoteConfiguration, executionEnvironment: ExecutionEnvironment): MonoConnectRemoteProfileState(remoteConfiguration, executionEnvironment) { + override val consoleKind: ConsoleKind = ConsoleKind.Normal +} \ No newline at end of file