1
1
package cc.unitmesh.devti.observer
2
2
3
3
import cc.unitmesh.devti.provider.observer.AgentObserver
4
+ import cc.unitmesh.devti.sketch.ui.patch.readText
5
+ import cc.unitmesh.devti.util.isInProject
4
6
import cc.unitmesh.devti.util.relativePath
7
+ import com.intellij.build.BuildView
8
+ import com.intellij.execution.filters.FileHyperlinkInfo
5
9
import com.intellij.execution.impl.ConsoleViewImpl
10
+ import com.intellij.execution.impl.EditorHyperlinkSupport
6
11
import com.intellij.execution.testframework.sm.runner.SMTRunnerEventsAdapter
7
12
import com.intellij.execution.testframework.sm.runner.SMTRunnerEventsListener
8
13
import com.intellij.execution.testframework.sm.runner.SMTestProxy
9
14
import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerConsoleView
10
15
import com.intellij.execution.ui.ExecutionConsole
11
16
import com.intellij.execution.ui.RunContentManager
12
17
import com.intellij.openapi.Disposable
13
- import com.intellij.openapi.application.runInEdt
14
18
import com.intellij.openapi.application.runReadAction
15
- import com.intellij.openapi.editor.Editor
16
19
import com.intellij.openapi.editor.markup.RangeHighlighter
20
+ import com.intellij.openapi.progress.ProgressIndicator
21
+ import com.intellij.openapi.progress.ProgressManager
22
+ import com.intellij.openapi.progress.Task
23
+ import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator
17
24
import com.intellij.openapi.project.Project
18
25
import com.intellij.openapi.util.TextRange
26
+ import com.intellij.openapi.vfs.VirtualFile
19
27
import com.intellij.psi.search.GlobalSearchScope
20
28
import com.intellij.psi.search.ProjectScope
21
29
import com.intellij.refactoring.suggested.range
22
30
import com.intellij.util.messages.MessageBusConnection
31
+ import kotlinx.coroutines.DelicateCoroutinesApi
32
+ import kotlinx.coroutines.GlobalScope
33
+ import kotlinx.coroutines.delay
34
+ import kotlinx.coroutines.launch
23
35
import java.lang.reflect.Field
24
36
25
37
class TestAgentObserver : AgentObserver , Disposable {
26
38
private var connection: MessageBusConnection ? = null
39
+
40
+ @OptIn(DelicateCoroutinesApi ::class )
27
41
override fun onRegister (project : Project ) {
28
42
connection = project.messageBus.connect()
29
43
connection?.subscribe(SMTRunnerEventsListener .TEST_STATUS , object : SMTRunnerEventsAdapter () {
30
44
override fun onTestFailed (test : SMTestProxy ) {
31
- sendResult(test, project, ProjectScope .getProjectScope(project))
45
+ GlobalScope .launch {
46
+ delay(3000 )
47
+ sendResult(test, project, ProjectScope .getProjectScope(project))
48
+ }
32
49
}
33
50
})
34
51
}
35
52
36
53
private fun sendResult (test : SMTestProxy , project : Project , searchScope : GlobalSearchScope ) {
37
- runInEdt {
38
- getConsoleEditor(project)
39
- val sourceCode = test.getLocation(project, searchScope)
40
- val psiElement = sourceCode?.psiElement
41
- val language = psiElement?.language?.displayName ? : " "
42
- val filepath = psiElement?.containingFile?.virtualFile?.relativePath(project) ? : " "
43
- val code = runReadAction<String > { psiElement?.text ? : " " }
44
- val prompt = """ Help me fix follow test issue:
54
+ val task = object : Task .Backgroundable (project, " Processing context" , false ) {
55
+ override fun run (indicator : ProgressIndicator ) {
56
+ val relatedCode = collectConsoleRelatedCode(project) ? : emptyList()
57
+ val sourceCode = runReadAction { test.getLocation(project, searchScope) }
58
+ val psiElement = sourceCode?.psiElement
59
+ val language = psiElement?.language?.displayName ? : " "
60
+ val filepath = psiElement?.containingFile?.virtualFile?.relativePath(project) ? : " "
61
+ val code = runReadAction<String > { psiElement?.text ? : " " }
62
+ val formatedRelatedCode = " \n ## related code:\n ```$language \n ${relatedCode.joinToString(" \n " )} \n ```"
63
+ val prompt = """ Help me fix follow test issue:
45
64
|## ErrorMessage:
46
65
|```
47
66
|${test.errorMessage}
@@ -54,57 +73,106 @@ class TestAgentObserver : AgentObserver, Disposable {
54
73
|```$language
55
74
|$code
56
75
|```
76
+ |${formatedRelatedCode}
57
77
|""" .trimMargin()
58
78
59
- sendErrorNotification(project, prompt)
79
+ sendErrorNotification(project, prompt)
80
+ }
60
81
}
82
+
83
+ ProgressManager .getInstance()
84
+ .runProcessWithProgressAsynchronously(task, BackgroundableProcessIndicator (task))
61
85
}
62
86
63
87
/* *
64
88
*```kotlin
65
89
* (content.component.components.firstOrNull() as? NonOpaquePanel)?.components?.firstOrNull { it is JBRunnerTabs }
66
90
*```
67
91
*/
68
- private fun getConsoleEditor (project : Project ): Editor ? {
92
+ private fun collectConsoleRelatedCode (project : Project ): List < String > ? {
69
93
val content = RunContentManager .getInstance(project).selectedContent ? : return null
70
94
val executionConsole = content.executionConsole ? : return null
71
95
val consoleViewImpl: ConsoleViewImpl = getConsoleView(executionConsole) ? : return null
72
96
val editor = consoleViewImpl.editor ? : return null
73
97
74
98
val startOffset = 0
75
- val allText = editor.document.text
76
99
val endOffset = editor.document.textLength
77
100
val textRange = TextRange (startOffset, endOffset)
78
101
val highlighters: Array <RangeHighlighter > = editor.markupModel.allHighlighters
79
102
80
- for (highlighter in highlighters) {
103
+ // / todo: first collect file path and line number, then build by range
104
+ val relatedCode = highlighters.mapNotNull { highlighter ->
81
105
if (textRange.contains(highlighter.range!! )) {
82
- // val hyperlinkInfo: FileHyperlinkInfo? = EditorHyperlinkSupport.getHyperlinkInfo(highlighter) as FileHyperlinkInfo
83
- // val descriptor = hyperlinkInfo?.descriptor ?: return null
84
- val range = highlighter.range!!
85
- val hyperlinkText = allText.substring(range.startOffset, range.endOffset)
86
- println (" hyperlinkText: $hyperlinkText " )
106
+ // call be .DiffHyperlink
107
+ val hyperlinkInfo: FileHyperlinkInfo ? = EditorHyperlinkSupport .getHyperlinkInfo(highlighter) as ? FileHyperlinkInfo
108
+ val descriptor = hyperlinkInfo?.descriptor ? : return @mapNotNull null
109
+ val virtualFile: VirtualFile = descriptor.file
110
+
111
+ val isProjectFile = runReadAction { project.isInProject(virtualFile) }
112
+ if (isProjectFile) {
113
+ val lineNumber = descriptor.line
114
+ val allText = virtualFile.readText()
115
+ val startLine = if (lineNumber - 10 < 0 ) {
116
+ 0
117
+ } else {
118
+ lineNumber - 10
119
+ }
120
+ // endLine should be less than allText.lines().size
121
+ val endLine = if (lineNumber + 10 > allText.lines().size) {
122
+ allText.lines().size
123
+ } else {
124
+ lineNumber + 10
125
+ }
126
+
127
+ return @mapNotNull allText.lines().subList(startLine, endLine).joinToString(" \n " )
128
+ } else {
129
+ return @mapNotNull null
130
+ }
131
+ } else {
132
+ return @mapNotNull null
87
133
}
88
134
}
89
135
90
- return consoleViewImpl.editor
136
+ return relatedCode.distinct()
91
137
}
92
138
139
+
93
140
private fun getConsoleView (executionConsole : ExecutionConsole ): ConsoleViewImpl ? {
94
- if (executionConsole is SMTRunnerConsoleView ) {
95
- try {
96
- val resultsViewerClass = executionConsole.resultsViewer::class .java
97
- val myConsoleViewField: Field = resultsViewerClass.getDeclaredField(" myConsoleView" )
98
- myConsoleViewField.isAccessible = true
99
- val myConsoleView = myConsoleViewField.get(executionConsole.resultsViewer) as ? ConsoleViewImpl
100
- return myConsoleView
101
- } catch (e: Exception ) {
102
- e.printStackTrace()
141
+ when (executionConsole) {
142
+ is ConsoleViewImpl -> {
143
+ return executionConsole
144
+ }
145
+
146
+ is BuildView -> {
147
+ when (executionConsole.consoleView) {
148
+ is SMTRunnerConsoleView -> {
149
+ return getTestView(executionConsole.consoleView as SMTRunnerConsoleView )
150
+ }
151
+ }
152
+ }
153
+
154
+ is SMTRunnerConsoleView -> {
155
+ return getTestView(executionConsole)
103
156
}
104
157
}
105
- return executionConsole as ? ConsoleViewImpl
158
+
159
+ return null
106
160
}
107
161
162
+ private fun getTestView (executionConsole : SMTRunnerConsoleView ): ConsoleViewImpl ? {
163
+ try {
164
+ val resultsViewerClass = executionConsole.resultsViewer::class .java
165
+ val myConsoleViewField: Field = resultsViewerClass.getDeclaredField(" myConsoleView" )
166
+ myConsoleViewField.isAccessible = true
167
+ val myConsoleView = myConsoleViewField.get(executionConsole.resultsViewer) as ? ConsoleViewImpl
168
+ return myConsoleView
169
+ } catch (e: Exception ) {
170
+ return null
171
+ }
172
+ }
173
+
174
+ // ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.DEBUG)
175
+
108
176
override fun dispose () {
109
177
connection?.disconnect()
110
178
}
0 commit comments