Skip to content

Commit 8e47d2e

Browse files
committed
feat(devins-kotlin): refactor RunService to use new ExecutionManager API #100
This commit refactors the `RunService` class to use the new ExecutionManager API introduced in IntelliJ IDEA 2022.2. The changes include:
1 parent 9e1df95 commit 8e47d2e

File tree

1 file changed

+198
-10
lines changed

1 file changed

+198
-10
lines changed

src/main/kotlin/cc/unitmesh/devti/provider/RunService.kt

Lines changed: 198 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,36 @@
11
package cc.unitmesh.devti.provider
22

3-
import com.intellij.execution.ExecutionManager
4-
import com.intellij.execution.Executor
5-
import com.intellij.execution.RunManager
6-
import com.intellij.execution.RunnerAndConfigurationSettings
3+
import com.intellij.execution.*
74
import com.intellij.execution.actions.ConfigurationContext
85
import com.intellij.execution.configurations.RunConfiguration
96
import com.intellij.execution.configurations.RunProfile
107
import com.intellij.execution.executors.DefaultRunExecutor
8+
import com.intellij.execution.impl.ExecutionManagerImpl
9+
import com.intellij.execution.process.*
10+
import com.intellij.execution.runners.ExecutionEnvironment
1111
import com.intellij.execution.runners.ExecutionEnvironmentBuilder
12-
import com.intellij.openapi.actionSystem.DataContext
12+
import com.intellij.execution.runners.ProgramRunner
13+
import com.intellij.execution.testframework.sm.runner.SMTRunnerEventsAdapter
14+
import com.intellij.execution.testframework.sm.runner.SMTRunnerEventsListener
15+
import com.intellij.execution.testframework.sm.runner.SMTestProxy
16+
import com.intellij.openapi.Disposable
17+
import com.intellij.openapi.application.invokeAndWaitIfNeeded
18+
import com.intellij.openapi.application.runInEdt
19+
import com.intellij.openapi.application.runReadAction
1320
import com.intellij.openapi.diagnostic.Logger
1421
import com.intellij.openapi.diagnostic.logger
22+
import com.intellij.openapi.progress.ProgressIndicator
1523
import com.intellij.openapi.project.Project
24+
import com.intellij.openapi.util.Disposer
25+
import com.intellij.openapi.util.Key
1626
import com.intellij.openapi.vfs.VirtualFile
1727
import com.intellij.psi.PsiElement
28+
import com.intellij.util.messages.MessageBusConnection
29+
import java.io.BufferedWriter
30+
import java.io.IOException
31+
import java.io.OutputStreamWriter
32+
import java.nio.charset.StandardCharsets
33+
import java.util.concurrent.CountDownLatch
1834

1935
interface RunService {
2036
private val logger: Logger get() = logger<RunService>()
@@ -90,16 +106,188 @@ interface RunService {
90106
settings = createDefaultTestConfigurations(project, testElement ?: return null) ?: return null
91107
}
92108

93-
val executor: Executor = DefaultRunExecutor.getRunExecutorInstance()
94-
val builder = ExecutionEnvironmentBuilder.createOrNull(executor, settings) ?: return null
109+
settings.isActivateToolWindowBeforeRun = false
95110

96-
val environment = builder.activeTarget().dataContext(DataContext.EMPTY_CONTEXT).build()
97-
ExecutionManager.getInstance(project).restartRunProfile(environment)
111+
val stderr = StringBuilder()
112+
val processListener = object : OutputListener() {
113+
override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) {
114+
val text = event.text
115+
if (text != null && ProcessOutputType.isStderr(outputType)) {
116+
stderr.append(text)
117+
}
118+
}
119+
}
120+
121+
val testRoots = mutableListOf<SMTestProxy.SMRootTestProxy>()
122+
val testEventsListener = object : SMTRunnerEventsAdapter() {
123+
override fun onTestingStarted(testsRoot: SMTestProxy.SMRootTestProxy) {
124+
testRoots += testsRoot
125+
}
126+
}
127+
128+
executeRunConfigures(project, settings, processListener, testEventsListener)
129+
130+
@Suppress("UnstableApiUsage")
131+
invokeAndWaitIfNeeded {}
132+
// val testResults = testRoots.map { it.toCheckResult() }
133+
134+
val output = processListener.output
135+
val errorOutput = output.stderr
136+
137+
if (output.exitCode != 0) {
138+
return errorOutput
139+
}
140+
141+
val outputString = output.stdout
142+
return outputString
143+
}
144+
145+
fun executeRunConfigures(
146+
project: Project,
147+
settings: RunnerAndConfigurationSettings,
148+
processListener: OutputListener,
149+
testEventsListener: SMTRunnerEventsAdapter
150+
) {
151+
val connection = project.messageBus.connect()
152+
try {
153+
return executeRunConfigurations(connection, settings, processListener, testEventsListener)
154+
} finally {
155+
// connection.disconnect()
156+
}
157+
}
158+
159+
private fun executeRunConfigurations(
160+
connection: MessageBusConnection,
161+
configurations: RunnerAndConfigurationSettings,
162+
processListener: ProcessListener?,
163+
testEventsListener: SMTRunnerEventsListener?
164+
) {
165+
testEventsListener?.let {
166+
connection.subscribe(SMTRunnerEventsListener.TEST_STATUS, it)
167+
}
168+
val context = Context(processListener, null, CountDownLatch(1))
169+
Disposer.register(connection, context)
170+
171+
runInEdt {
172+
connection.subscribe(
173+
ExecutionManager.EXECUTION_TOPIC,
174+
CheckExecutionListener(DefaultRunExecutor.EXECUTOR_ID, context)
175+
)
176+
177+
configurations.startRunConfigurationExecution(context)
178+
}
98179

99-
return null
180+
// if run in Task, Disposer.dispose(context)
100181
}
101182

183+
184+
/**
185+
* Returns `true` if configuration execution is started successfully, `false` otherwise
186+
*/
187+
@Throws(ExecutionException::class)
188+
private fun RunnerAndConfigurationSettings.startRunConfigurationExecution(context: Context): Boolean {
189+
val runner = ProgramRunner.getRunner(DefaultRunExecutor.EXECUTOR_ID, configuration)
190+
val env =
191+
ExecutionEnvironmentBuilder.create(DefaultRunExecutor.getRunExecutorInstance(), this).activeTarget().build()
192+
193+
if (runner == null || env.state == null) {
194+
context.latch.countDown()
195+
return false
196+
}
197+
198+
199+
@Suppress("UnstableApiUsage")
200+
env.callback = ProgramRunner.Callback { descriptor ->
201+
// Descriptor can be null in some cases.
202+
// For example, IntelliJ Rust's test runner provides null here if compilation fails
203+
if (descriptor == null) {
204+
context.latch.countDown()
205+
return@Callback
206+
}
207+
208+
Disposer.register(context, Disposable {
209+
ExecutionManagerImpl.stopProcess(descriptor)
210+
})
211+
val processHandler = descriptor.processHandler
212+
if (processHandler != null) {
213+
processHandler.addProcessListener(object : ProcessAdapter() {
214+
override fun processTerminated(event: ProcessEvent) {
215+
context.latch.countDown()
216+
}
217+
})
218+
context.processListener?.let { processHandler.addProcessListener(it) }
219+
}
220+
}
221+
222+
context.environments.add(env)
223+
runner.execute(env)
224+
return true
225+
}
226+
227+
102228
fun createDefaultTestConfigurations(project: Project, element: PsiElement): RunnerAndConfigurationSettings? {
103229
return ConfigurationContext(element).configurationsFromContext?.firstOrNull()?.configurationSettings
104230
}
105231
}
232+
233+
private class CheckExecutionListener(
234+
private val executorId: String,
235+
private val context: Context,
236+
) : ExecutionListener {
237+
override fun processStartScheduled(executorId: String, env: ExecutionEnvironment) {
238+
checkAndExecute(executorId, env) {
239+
context.executionListener?.processStartScheduled(executorId, env)
240+
}
241+
}
242+
243+
override fun processNotStarted(executorId: String, env: ExecutionEnvironment) {
244+
checkAndExecute(executorId, env) {
245+
context.latch.countDown()
246+
context.executionListener?.processNotStarted(executorId, env)
247+
}
248+
}
249+
250+
override fun processStarting(executorId: String, env: ExecutionEnvironment) {
251+
checkAndExecute(executorId, env) {
252+
context.executionListener?.processStarting(executorId, env)
253+
}
254+
}
255+
256+
override fun processStarted(executorId: String, env: ExecutionEnvironment, handler: ProcessHandler) {
257+
checkAndExecute(executorId, env) {
258+
context.executionListener?.processStarted(executorId, env, handler)
259+
}
260+
}
261+
262+
override fun processTerminating(executorId: String, env: ExecutionEnvironment, handler: ProcessHandler) {
263+
checkAndExecute(executorId, env) {
264+
context.executionListener?.processTerminating(executorId, env, handler)
265+
}
266+
}
267+
268+
override fun processTerminated(
269+
executorId: String,
270+
env: ExecutionEnvironment,
271+
handler: ProcessHandler,
272+
exitCode: Int
273+
) {
274+
checkAndExecute(executorId, env) {
275+
context.executionListener?.processTerminated(executorId, env, handler, exitCode)
276+
}
277+
}
278+
279+
private fun checkAndExecute(executorId: String, env: ExecutionEnvironment, action: () -> Unit) {
280+
if (this.executorId == executorId && env in context.environments) {
281+
action()
282+
}
283+
}
284+
}
285+
286+
class Context(
287+
val processListener: ProcessListener?,
288+
val executionListener: ExecutionListener?,
289+
val latch: CountDownLatch
290+
) : Disposable {
291+
val environments: MutableList<ExecutionEnvironment> = mutableListOf()
292+
override fun dispose() {}
293+
}

0 commit comments

Comments
 (0)