Skip to content

Commit 73d08e6

Browse files
committed
refactor: extract to run service #100
1 parent cdec106 commit 73d08e6

File tree

3 files changed

+181
-142
lines changed

3 files changed

+181
-142
lines changed
Lines changed: 6 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,15 @@
11
package cc.unitmesh.devti.provider
22

3-
import com.intellij.execution.*
4-
import com.intellij.execution.actions.ConfigurationContext
3+
import com.intellij.execution.RunManager
4+
import com.intellij.execution.RunnerAndConfigurationSettings
55
import com.intellij.execution.configurations.RunConfiguration
66
import com.intellij.execution.configurations.RunProfile
7-
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.ExecutionEnvironmentBuilder
11-
import com.intellij.execution.runners.ProgramRunner
12-
import com.intellij.execution.testframework.sm.runner.SMTRunnerEventsAdapter
13-
import com.intellij.execution.testframework.sm.runner.SMTRunnerEventsListener
14-
import com.intellij.execution.testframework.sm.runner.SMTestProxy
15-
import com.intellij.openapi.Disposable
16-
import com.intellij.openapi.application.invokeAndWaitIfNeeded
17-
import com.intellij.openapi.application.runInEdt
187
import com.intellij.openapi.diagnostic.Logger
198
import com.intellij.openapi.diagnostic.logger
9+
import com.intellij.openapi.progress.ProgressManager
2010
import com.intellij.openapi.project.Project
21-
import com.intellij.openapi.util.Disposer
22-
import com.intellij.openapi.util.Key
2311
import com.intellij.openapi.vfs.VirtualFile
2412
import com.intellij.psi.PsiElement
25-
import com.intellij.util.messages.MessageBusConnection
26-
import java.util.concurrent.CountDownLatch
2713

2814
interface RunService {
2915
private val logger: Logger get() = logger<RunService>()
@@ -94,132 +80,10 @@ interface RunService {
9480
* @return The result of the run operation, or `null` if an error occurred.
9581
*/
9682
fun runFile(project: Project, virtualFile: VirtualFile, testElement: PsiElement?): String? {
97-
var settings: RunnerAndConfigurationSettings? = createRunSettings(project, virtualFile)
98-
if (settings == null) {
99-
settings = createDefaultTestConfigurations(project, testElement ?: return null) ?: return null
100-
}
101-
102-
settings.isActivateToolWindowBeforeRun = false
103-
104-
val stderr = StringBuilder()
105-
val processListener = object : OutputListener() {
106-
override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) {
107-
val text = event.text
108-
if (text != null && ProcessOutputType.isStderr(outputType)) {
109-
stderr.append(text)
110-
}
111-
}
112-
}
113-
114-
val testRoots = mutableListOf<SMTestProxy.SMRootTestProxy>()
115-
val testEventsListener = object : SMTRunnerEventsAdapter() {
116-
override fun onTestingStarted(testsRoot: SMTestProxy.SMRootTestProxy) {
117-
testRoots += testsRoot
118-
}
119-
}
120-
121-
executeRunConfigures(project, settings, processListener, testEventsListener)
122-
123-
@Suppress("UnstableApiUsage")
124-
invokeAndWaitIfNeeded {}
125-
// val testResults = testRoots.map { it.toCheckResult() }
126-
127-
val output = processListener.output
128-
val errorOutput = output.stderr
129-
130-
if (output.exitCode != 0) {
131-
return errorOutput
132-
}
133-
134-
val outputString = output.stdout
135-
return outputString
136-
}
137-
138-
fun executeRunConfigures(
139-
project: Project,
140-
settings: RunnerAndConfigurationSettings,
141-
processListener: OutputListener,
142-
testEventsListener: SMTRunnerEventsAdapter
143-
) {
144-
val connection = project.messageBus.connect()
145-
try {
146-
return executeRunConfigurations(connection, settings, processListener, testEventsListener)
147-
} finally {
148-
// connection.disconnect()
149-
}
150-
}
151-
152-
private fun executeRunConfigurations(
153-
connection: MessageBusConnection,
154-
configurations: RunnerAndConfigurationSettings,
155-
processListener: ProcessListener?,
156-
testEventsListener: SMTRunnerEventsListener?
157-
) {
158-
testEventsListener?.let {
159-
connection.subscribe(SMTRunnerEventsListener.TEST_STATUS, it)
160-
}
161-
val context = Context(processListener, null, CountDownLatch(1))
162-
Disposer.register(connection, context)
163-
164-
runInEdt {
165-
connection.subscribe(
166-
ExecutionManager.EXECUTION_TOPIC,
167-
CheckExecutionListener(DefaultRunExecutor.EXECUTOR_ID, context)
168-
)
169-
170-
configurations.startRunConfigurationExecution(context)
171-
}
172-
173-
// if run in Task, Disposer.dispose(context)
174-
}
175-
176-
177-
/**
178-
* Returns `true` if configuration execution is started successfully, `false` otherwise
179-
*/
180-
@Throws(ExecutionException::class)
181-
private fun RunnerAndConfigurationSettings.startRunConfigurationExecution(context: Context): Boolean {
182-
val runner = ProgramRunner.getRunner(DefaultRunExecutor.EXECUTOR_ID, configuration)
183-
val env =
184-
ExecutionEnvironmentBuilder.create(DefaultRunExecutor.getRunExecutorInstance(), this)
185-
.activeTarget()
186-
.build(callback(context))
187-
188-
if (runner == null || env.state == null) {
189-
context.latch.countDown()
190-
return false
191-
}
192-
193-
context.environments.add(env)
194-
runner.execute(env)
195-
return true
196-
}
197-
198-
fun callback(context: Context) = ProgramRunner.Callback { descriptor ->
199-
// Descriptor can be null in some cases.
200-
// For example, IntelliJ Rust's test runner provides null here if compilation fails
201-
if (descriptor == null) {
202-
context.latch.countDown()
203-
return@Callback
204-
}
205-
206-
Disposer.register(context) {
207-
ExecutionManagerImpl.stopProcess(descriptor)
208-
}
209-
val processHandler = descriptor.processHandler
210-
if (processHandler != null) {
211-
processHandler.addProcessListener(object : ProcessAdapter() {
212-
override fun processTerminated(event: ProcessEvent) {
213-
context.latch.countDown()
214-
}
215-
})
216-
context.processListener?.let { processHandler.addProcessListener(it) }
217-
}
218-
}
219-
83+
val runTask = RunServiceTask(project, virtualFile, testElement, this)
84+
ProgressManager.getInstance().run(runTask)
22085

221-
fun createDefaultTestConfigurations(project: Project, element: PsiElement): RunnerAndConfigurationSettings? {
222-
return ConfigurationContext(element).configurationsFromContext?.firstOrNull()?.configurationSettings
86+
return null
22387
}
22488
}
22589

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package cc.unitmesh.devti.provider
2+
3+
import cc.unitmesh.devti.AutoDevBundle
4+
import com.intellij.execution.ExecutionException
5+
import com.intellij.execution.ExecutionManager
6+
import com.intellij.execution.OutputListener
7+
import com.intellij.execution.RunnerAndConfigurationSettings
8+
import com.intellij.execution.actions.ConfigurationContext
9+
import com.intellij.execution.executors.DefaultRunExecutor
10+
import com.intellij.execution.impl.ExecutionManagerImpl
11+
import com.intellij.execution.process.ProcessAdapter
12+
import com.intellij.execution.process.ProcessEvent
13+
import com.intellij.execution.process.ProcessListener
14+
import com.intellij.execution.process.ProcessOutputType
15+
import com.intellij.execution.runners.ExecutionEnvironmentBuilder
16+
import com.intellij.execution.runners.ProgramRunner
17+
import com.intellij.execution.testframework.sm.runner.SMTRunnerEventsAdapter
18+
import com.intellij.execution.testframework.sm.runner.SMTRunnerEventsListener
19+
import com.intellij.execution.testframework.sm.runner.SMTestProxy
20+
import com.intellij.openapi.application.invokeAndWaitIfNeeded
21+
import com.intellij.openapi.application.runInEdt
22+
import com.intellij.openapi.progress.ProgressIndicator
23+
import com.intellij.openapi.project.Project
24+
import com.intellij.openapi.util.Disposer
25+
import com.intellij.openapi.util.Key
26+
import com.intellij.openapi.vfs.VirtualFile
27+
import com.intellij.psi.PsiElement
28+
import com.intellij.util.messages.MessageBusConnection
29+
import java.util.concurrent.CountDownLatch
30+
31+
class RunServiceTask(
32+
private val project: Project,
33+
private val virtualFile: VirtualFile,
34+
private val testElement: PsiElement?,
35+
private val runService: RunService
36+
) : com.intellij.openapi.progress.Task.Backgroundable(
37+
project,
38+
AutoDevBundle.message("progress.run.task"),
39+
true
40+
) {
41+
override fun run(indicator: ProgressIndicator) {
42+
doRun(indicator)
43+
}
44+
45+
private fun doRun(indicator: ProgressIndicator): String? {
46+
var settings: RunnerAndConfigurationSettings? = runService.createRunSettings(project, virtualFile)
47+
if (settings == null) {
48+
settings = createDefaultTestConfigurations(project, testElement ?: return null) ?: return null
49+
}
50+
51+
settings.isActivateToolWindowBeforeRun = false
52+
53+
val stderr = StringBuilder()
54+
val processListener = object : OutputListener() {
55+
override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) {
56+
val text = event.text
57+
if (text != null && ProcessOutputType.isStderr(outputType)) {
58+
stderr.append(text)
59+
}
60+
}
61+
}
62+
63+
val testRoots = mutableListOf<SMTestProxy.SMRootTestProxy>()
64+
val testEventsListener = object : SMTRunnerEventsAdapter() {
65+
override fun onTestingStarted(testsRoot: SMTestProxy.SMRootTestProxy) {
66+
testRoots += testsRoot
67+
}
68+
}
69+
70+
executeRunConfigures(project, settings, processListener, testEventsListener)
71+
72+
@Suppress("UnstableApiUsage")
73+
invokeAndWaitIfNeeded {}
74+
// val testResults = testRoots.map { it.toCheckResult() }
75+
76+
val output = processListener.output
77+
val errorOutput = output.stderr
78+
79+
if (output.exitCode != 0) {
80+
return errorOutput
81+
}
82+
83+
val outputString = output.stdout
84+
return outputString
85+
}
86+
87+
88+
fun executeRunConfigures(
89+
project: Project,
90+
settings: RunnerAndConfigurationSettings,
91+
processListener: OutputListener,
92+
testEventsListener: SMTRunnerEventsAdapter
93+
) {
94+
val connection = project.messageBus.connect()
95+
try {
96+
return executeRunConfigurations(connection, settings, processListener, testEventsListener)
97+
} finally {
98+
// connection.disconnect()
99+
}
100+
}
101+
102+
private fun executeRunConfigurations(
103+
connection: MessageBusConnection,
104+
configurations: RunnerAndConfigurationSettings,
105+
processListener: ProcessListener?,
106+
testEventsListener: SMTRunnerEventsListener?
107+
) {
108+
testEventsListener?.let {
109+
connection.subscribe(SMTRunnerEventsListener.TEST_STATUS, it)
110+
}
111+
val context = Context(processListener, null, CountDownLatch(1))
112+
Disposer.register(connection, context)
113+
114+
runInEdt {
115+
connection.subscribe(
116+
ExecutionManager.EXECUTION_TOPIC,
117+
CheckExecutionListener(DefaultRunExecutor.EXECUTOR_ID, context)
118+
)
119+
120+
configurations.startRunConfigurationExecution(context)
121+
}
122+
123+
// if run in Task, Disposer.dispose(context)
124+
}
125+
126+
127+
/**
128+
* Returns `true` if configuration execution is started successfully, `false` otherwise
129+
*/
130+
@Throws(ExecutionException::class)
131+
private fun RunnerAndConfigurationSettings.startRunConfigurationExecution(context: Context): Boolean {
132+
val runner = ProgramRunner.getRunner(DefaultRunExecutor.EXECUTOR_ID, configuration)
133+
val env =
134+
ExecutionEnvironmentBuilder.create(DefaultRunExecutor.getRunExecutorInstance(), this)
135+
.activeTarget()
136+
.build(callback(context))
137+
138+
if (runner == null || env.state == null) {
139+
context.latch.countDown()
140+
return false
141+
}
142+
143+
context.environments.add(env)
144+
runner.execute(env)
145+
return true
146+
}
147+
148+
fun callback(context: Context) = ProgramRunner.Callback { descriptor ->
149+
// Descriptor can be null in some cases.
150+
// For example, IntelliJ Rust's test runner provides null here if compilation fails
151+
if (descriptor == null) {
152+
context.latch.countDown()
153+
return@Callback
154+
}
155+
156+
Disposer.register(context) {
157+
ExecutionManagerImpl.stopProcess(descriptor)
158+
}
159+
val processHandler = descriptor.processHandler
160+
if (processHandler != null) {
161+
processHandler.addProcessListener(object : ProcessAdapter() {
162+
override fun processTerminated(event: ProcessEvent) {
163+
context.latch.countDown()
164+
}
165+
})
166+
context.processListener?.let { processHandler.addProcessListener(it) }
167+
}
168+
}
169+
170+
171+
fun createDefaultTestConfigurations(project: Project, element: PsiElement): RunnerAndConfigurationSettings? {
172+
return ConfigurationContext(element).configurationsFromContext?.firstOrNull()?.configurationSettings
173+
}
174+
}

src/main/resources/messages/AutoDevBundle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,4 @@ autoarkui.generate.design=Design Page
159159
# Inlay
160160
intentions.chat.inlay.complete.name = Inlay Complete Code
161161
intentions.chat.inlay.complete.family.name = Inlay Complete code
162+
progress.run.task=Running task

0 commit comments

Comments
 (0)