Skip to content

Commit

Permalink
feat(devins-kotlin): refactor RunService to use new ExecutionManager …
Browse files Browse the repository at this point in the history
…API #100

This commit refactors the `RunService` class to use the new ExecutionManager API introduced in IntelliJ IDEA 2022.2. The changes include:
  • Loading branch information
phodal committed Mar 25, 2024
1 parent 9e1df95 commit 8e47d2e
Showing 1 changed file with 198 additions and 10 deletions.
208 changes: 198 additions & 10 deletions src/main/kotlin/cc/unitmesh/devti/provider/RunService.kt
@@ -1,20 +1,36 @@
package cc.unitmesh.devti.provider

import com.intellij.execution.ExecutionManager
import com.intellij.execution.Executor
import com.intellij.execution.RunManager
import com.intellij.execution.RunnerAndConfigurationSettings
import com.intellij.execution.*
import com.intellij.execution.actions.ConfigurationContext
import com.intellij.execution.configurations.RunConfiguration
import com.intellij.execution.configurations.RunProfile
import com.intellij.execution.executors.DefaultRunExecutor
import com.intellij.execution.impl.ExecutionManagerImpl
import com.intellij.execution.process.*
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.execution.runners.ExecutionEnvironmentBuilder
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.execution.runners.ProgramRunner
import com.intellij.execution.testframework.sm.runner.SMTRunnerEventsAdapter
import com.intellij.execution.testframework.sm.runner.SMTRunnerEventsListener
import com.intellij.execution.testframework.sm.runner.SMTestProxy
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.invokeAndWaitIfNeeded
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.Key
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElement
import com.intellij.util.messages.MessageBusConnection
import java.io.BufferedWriter
import java.io.IOException
import java.io.OutputStreamWriter
import java.nio.charset.StandardCharsets
import java.util.concurrent.CountDownLatch

interface RunService {
private val logger: Logger get() = logger<RunService>()
Expand Down Expand Up @@ -90,16 +106,188 @@ interface RunService {
settings = createDefaultTestConfigurations(project, testElement ?: return null) ?: return null
}

val executor: Executor = DefaultRunExecutor.getRunExecutorInstance()
val builder = ExecutionEnvironmentBuilder.createOrNull(executor, settings) ?: return null
settings.isActivateToolWindowBeforeRun = false

val environment = builder.activeTarget().dataContext(DataContext.EMPTY_CONTEXT).build()
ExecutionManager.getInstance(project).restartRunProfile(environment)
val stderr = StringBuilder()
val processListener = object : OutputListener() {
override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) {
val text = event.text
if (text != null && ProcessOutputType.isStderr(outputType)) {
stderr.append(text)
}
}
}

val testRoots = mutableListOf<SMTestProxy.SMRootTestProxy>()
val testEventsListener = object : SMTRunnerEventsAdapter() {
override fun onTestingStarted(testsRoot: SMTestProxy.SMRootTestProxy) {
testRoots += testsRoot
}
}

executeRunConfigures(project, settings, processListener, testEventsListener)

@Suppress("UnstableApiUsage")
invokeAndWaitIfNeeded {}
// val testResults = testRoots.map { it.toCheckResult() }

val output = processListener.output
val errorOutput = output.stderr

if (output.exitCode != 0) {
return errorOutput
}

val outputString = output.stdout
return outputString
}

fun executeRunConfigures(
project: Project,
settings: RunnerAndConfigurationSettings,
processListener: OutputListener,
testEventsListener: SMTRunnerEventsAdapter
) {
val connection = project.messageBus.connect()
try {
return executeRunConfigurations(connection, settings, processListener, testEventsListener)
} finally {
// connection.disconnect()
}
}

private fun executeRunConfigurations(
connection: MessageBusConnection,
configurations: RunnerAndConfigurationSettings,
processListener: ProcessListener?,
testEventsListener: SMTRunnerEventsListener?
) {
testEventsListener?.let {
connection.subscribe(SMTRunnerEventsListener.TEST_STATUS, it)
}
val context = Context(processListener, null, CountDownLatch(1))
Disposer.register(connection, context)

runInEdt {
connection.subscribe(
ExecutionManager.EXECUTION_TOPIC,
CheckExecutionListener(DefaultRunExecutor.EXECUTOR_ID, context)
)

configurations.startRunConfigurationExecution(context)
}

return null
// if run in Task, Disposer.dispose(context)
}


/**
* Returns `true` if configuration execution is started successfully, `false` otherwise
*/
@Throws(ExecutionException::class)
private fun RunnerAndConfigurationSettings.startRunConfigurationExecution(context: Context): Boolean {
val runner = ProgramRunner.getRunner(DefaultRunExecutor.EXECUTOR_ID, configuration)
val env =
ExecutionEnvironmentBuilder.create(DefaultRunExecutor.getRunExecutorInstance(), this).activeTarget().build()

if (runner == null || env.state == null) {
context.latch.countDown()
return false
}


@Suppress("UnstableApiUsage")
env.callback = ProgramRunner.Callback { descriptor ->
// Descriptor can be null in some cases.
// For example, IntelliJ Rust's test runner provides null here if compilation fails
if (descriptor == null) {
context.latch.countDown()
return@Callback
}

Disposer.register(context, Disposable {
ExecutionManagerImpl.stopProcess(descriptor)
})
val processHandler = descriptor.processHandler
if (processHandler != null) {
processHandler.addProcessListener(object : ProcessAdapter() {
override fun processTerminated(event: ProcessEvent) {
context.latch.countDown()
}
})
context.processListener?.let { processHandler.addProcessListener(it) }
}
}

context.environments.add(env)
runner.execute(env)
return true
}


fun createDefaultTestConfigurations(project: Project, element: PsiElement): RunnerAndConfigurationSettings? {
return ConfigurationContext(element).configurationsFromContext?.firstOrNull()?.configurationSettings
}
}

private class CheckExecutionListener(
private val executorId: String,
private val context: Context,
) : ExecutionListener {
override fun processStartScheduled(executorId: String, env: ExecutionEnvironment) {
checkAndExecute(executorId, env) {
context.executionListener?.processStartScheduled(executorId, env)
}
}

override fun processNotStarted(executorId: String, env: ExecutionEnvironment) {
checkAndExecute(executorId, env) {
context.latch.countDown()
context.executionListener?.processNotStarted(executorId, env)
}
}

override fun processStarting(executorId: String, env: ExecutionEnvironment) {
checkAndExecute(executorId, env) {
context.executionListener?.processStarting(executorId, env)
}
}

override fun processStarted(executorId: String, env: ExecutionEnvironment, handler: ProcessHandler) {
checkAndExecute(executorId, env) {
context.executionListener?.processStarted(executorId, env, handler)
}
}

override fun processTerminating(executorId: String, env: ExecutionEnvironment, handler: ProcessHandler) {
checkAndExecute(executorId, env) {
context.executionListener?.processTerminating(executorId, env, handler)
}
}

override fun processTerminated(
executorId: String,
env: ExecutionEnvironment,
handler: ProcessHandler,
exitCode: Int
) {
checkAndExecute(executorId, env) {
context.executionListener?.processTerminated(executorId, env, handler, exitCode)
}
}

private fun checkAndExecute(executorId: String, env: ExecutionEnvironment, action: () -> Unit) {
if (this.executorId == executorId && env in context.environments) {
action()
}
}
}

class Context(
val processListener: ProcessListener?,
val executionListener: ExecutionListener?,
val latch: CountDownLatch
) : Disposable {
val environments: MutableList<ExecutionEnvironment> = mutableListOf()
override fun dispose() {}
}

0 comments on commit 8e47d2e

Please sign in to comment.