Skip to content

Commit e07a0b2

Browse files
committed
feat(terminal): implement real-time UI updating for terminal output
- Add UIUpdatingWriter to handle real-time output updates - Update executeAction to use UIUpdatingWriter for std and err output- Improve error handling and UI feedback during execution - Refactor result panel expansion and title updates
1 parent 1b8b4e9 commit e07a0b2

File tree

2 files changed

+131
-22
lines changed

2 files changed

+131
-22
lines changed

exts/ext-terminal/src/main/kotlin/cc/unitmesh/terminal/sketch/TerminalSketchProvider.kt

Lines changed: 63 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -113,36 +113,76 @@ class TerminalSketchProvider : LanguageSketchProvider {
113113
val executeAction = object :
114114
AnAction("Execute", AutoDevBundle.message("sketch.terminal.execute"), AllIcons.Actions.Execute) {
115115
override fun actionPerformed(e: AnActionEvent) {
116+
// Using our standalone writer to update UI in real-time
117+
val stdWriter = UIUpdatingWriter(
118+
onTextUpdate = { text, complete ->
119+
resultSketch.updateViewText(text, complete)
120+
},
121+
onPanelUpdate = { title, _ ->
122+
collapsibleResultPanel.setTitle(title)
123+
},
124+
checkCollapsed = {
125+
collapsibleResultPanel.isCollapsed()
126+
},
127+
expandPanel = {
128+
collapsibleResultPanel.expand()
129+
}
130+
)
131+
132+
val errWriter = UIUpdatingWriter(
133+
onTextUpdate = { text, complete ->
134+
resultSketch.updateViewText(text, complete)
135+
},
136+
onPanelUpdate = { title, _ ->
137+
collapsibleResultPanel.setTitle(title)
138+
},
139+
checkCollapsed = {
140+
collapsibleResultPanel.isCollapsed()
141+
},
142+
expandPanel = {
143+
collapsibleResultPanel.expand()
144+
}
145+
)
146+
147+
// Reset result and prepare UI for execution
148+
resultSketch.updateViewText("", true)
149+
stdWriter.setExecuting(true)
150+
116151
AutoDevCoroutineScope.scope(project).launch {
117152
val executor = ProcessExecutor(project)
118-
val runResult = executor.executeCode(
119-
getViewText(),
120-
PooledThreadExecutor.INSTANCE.asCoroutineDispatcher()
121-
)
122-
123-
ApplicationManager.getApplication().invokeLater {
124-
val resultText = if (runResult.exitCode != 0) {
125-
"${runResult.stdOutput}\n${runResult.errOutput}".trim()
126-
} else {
127-
runResult.stdOutput
153+
try {
154+
// Direct call to exec instead of executeCode
155+
val exitCode = executor.exec(
156+
getViewText(),
157+
stdWriter,
158+
errWriter,
159+
PooledThreadExecutor.INSTANCE.asCoroutineDispatcher()
160+
)
161+
162+
// Execution finished
163+
ApplicationManager.getApplication().invokeLater {
164+
stdWriter.setExecuting(false)
165+
166+
// Ensure result panel is expanded
167+
if (collapsibleResultPanel.isCollapsed()) {
168+
collapsibleResultPanel.expand()
169+
}
128170
}
129-
130-
resultSketch.updateViewText(resultText, true)
131-
132-
if (collapsibleResultPanel.isCollapsed()) {
133-
collapsibleResultPanel.expand()
134-
}
135-
136-
if (runResult.exitCode != 0) {
137-
collapsibleResultPanel.setTitle("Execution Results (Error: ${runResult.exitCode})")
138-
} else {
139-
collapsibleResultPanel.setTitle("Execution Results")
171+
} catch (ex: Exception) {
172+
ApplicationManager.getApplication().invokeLater {
173+
stdWriter.setExecuting(false)
174+
resultSketch.updateViewText(
175+
"${stdWriter.getContent()}\nError: ${ex.message}",
176+
true
177+
)
178+
collapsibleResultPanel.setTitle("Execution Results (Error)")
140179
}
141180
}
142181
}
143182
}
144183
}
145184

185+
// ... existing code for other actions ...
146186
val copyAction = object :
147187
AnAction("Copy", AutoDevBundle.message("sketch.terminal.copy.text"), AllIcons.Actions.Copy) {
148188
override fun actionPerformed(e: AnActionEvent) {
@@ -181,6 +221,7 @@ class TerminalSketchProvider : LanguageSketchProvider {
181221
return listOf(executeAction, copyAction, sendAction, popupAction)
182222
}
183223

224+
// ... existing code ...
184225
private fun executePopup(terminalWidget: JBTerminalWidget?, project: Project): MouseAdapter =
185226
object : MouseAdapter() {
186227
override fun mouseClicked(e: MouseEvent?) {
@@ -240,7 +281,7 @@ class TerminalSketchProvider : LanguageSketchProvider {
240281
override fun updateLanguage(language: Language?, originLanguage: String?) {}
241282
override fun dispose() {
242283
codeSketch.dispose()
243-
resultSketch.dispose() // Make sure to dispose resultSketch
284+
resultSketch.dispose()
244285
}
245286
}
246287
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package cc.unitmesh.terminal.sketch
2+
3+
import com.intellij.openapi.application.ApplicationManager
4+
import java.io.StringWriter
5+
import java.io.Writer
6+
7+
/**
8+
* A Writer implementation that updates UI components based on the output stream.
9+
* Uses callbacks to abstract UI interactions.
10+
*/
11+
class UIUpdatingWriter(
12+
private val onTextUpdate: (String, Boolean) -> Unit,
13+
private val onPanelUpdate: (String, Boolean) -> Unit,
14+
private val checkCollapsed: () -> Boolean,
15+
private val expandPanel: () -> Unit
16+
) : Writer() {
17+
private val stringWriter = StringWriter()
18+
private var isExecuting = false
19+
20+
override fun write(cbuf: CharArray, off: Int, len: Int) {
21+
stringWriter.write(cbuf, off, len)
22+
updateUI()
23+
}
24+
25+
override fun flush() {
26+
stringWriter.flush()
27+
updateUI()
28+
}
29+
30+
override fun close() {
31+
stringWriter.close()
32+
isExecuting = false
33+
updateUI()
34+
}
35+
36+
fun setExecuting(executing: Boolean) {
37+
isExecuting = executing
38+
updateUI()
39+
}
40+
41+
fun updateUI() {
42+
ApplicationManager.getApplication().invokeLater {
43+
val currentText = stringWriter.toString()
44+
onTextUpdate(currentText, !isExecuting)
45+
46+
if (checkCollapsed()) {
47+
expandPanel()
48+
}
49+
50+
if (isExecuting) {
51+
onPanelUpdate("Execution Results (Running...)", isExecuting)
52+
} else {
53+
if (currentText.contains("EXIT_CODE: 0")) {
54+
onPanelUpdate("Execution Results (Success)", isExecuting)
55+
} else if (currentText.contains("EXIT_CODE:")) {
56+
val exitCodePattern = "EXIT_CODE: (\\d+)".toRegex()
57+
val match = exitCodePattern.find(currentText)
58+
val exitCode = match?.groupValues?.get(1) ?: "Error"
59+
onPanelUpdate("Execution Results (Error: $exitCode)", isExecuting)
60+
} else {
61+
onPanelUpdate("Execution Results", isExecuting)
62+
}
63+
}
64+
}
65+
}
66+
67+
fun getContent(): String = stringWriter.toString()
68+
}

0 commit comments

Comments
 (0)