fix: render new panels after tool updates

This commit is contained in:
Carl-Robert Linnupuu 2026-04-27 14:59:11 +01:00
parent a10014da6d
commit 876b20cd3b
3 changed files with 108 additions and 4 deletions

View file

@ -153,11 +153,11 @@ public class ChatMessageResponseBody extends JPanel {
}
public void addToolStatusPanel(JComponent component) {
currentlyProcessedTextPane = null;
currentlyProcessedEditorPanel = null;
currentlyProcessedMermaidPanel = null;
streamOutputParser.clear();
finishCurrentStreamingSection();
streamOutputParser.startNewVisualSection();
contentPanel.add(component);
contentPanel.revalidate();
contentPanel.repaint();
}
public void updateMessage(String partialMessage) {
@ -379,6 +379,12 @@ public class ChatMessageResponseBody extends JPanel {
}
}
private void finishCurrentStreamingSection() {
currentlyProcessedTextPane = null;
currentlyProcessedEditorPanel = null;
currentlyProcessedMermaidPanel = null;
}
private void processCode(Segment item) {
if (isMermaidCode(item)) {
processMermaid(item.getContent());

View file

@ -26,6 +26,18 @@ class SseMessageParser : MessageParser {
buffer.clear()
}
fun startNewVisualSection() {
buffer.clear()
parserState = when (val state = parserState) {
is ParserState.Outside -> ParserState.Outside
is ParserState.CodeHeaderWaiting -> state.copy(content = "")
is ParserState.InCode -> state.copy(content = "")
is ParserState.InSearch -> state.copy(searchContent = "")
is ParserState.InReplace -> state.copy(searchContent = "", replaceContent = "")
is ParserState.InThinking -> ParserState.InThinking()
}
}
override fun parse(input: String): List<Segment> {
val segments = mutableListOf<Segment>()
var position = 0

View file

@ -0,0 +1,86 @@
package ee.carlrobert.codegpt.toolwindow.chat.ui
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.EditorFactory
import com.intellij.openapi.util.Disposer
import ee.carlrobert.codegpt.toolwindow.chat.editor.ResponseEditorPanel
import org.assertj.core.api.Assertions.assertThat
import testsupport.IntegrationTest
import java.awt.Component
import java.awt.Container
import javax.swing.JPanel
import javax.swing.JTextPane
class ChatMessageResponseBodyTest : IntegrationTest() {
fun testToolPanelStartsNewTextPaneForSubsequentStreamedText() {
ApplicationManager.getApplication().invokeAndWait {
val disposable = Disposer.newDisposable()
try {
val responseBody = ChatMessageResponseBody(project, false, disposable)
responseBody.updateMessage("Hello")
responseBody.addToolStatusPanel(JPanel())
responseBody.updateMessage(" world")
val textPanes = findComponents(responseBody, JTextPane::class.java)
assertThat(textPanes).hasSize(2)
assertThat(textPanes.first().text).contains("Hello")
assertThat(textPanes.last().text).contains("world")
assertThat(textPanes.last().text).doesNotContain("Hello")
} finally {
Disposer.dispose(disposable)
}
}
}
fun testToolPanelStartsNewCodePanelForSubsequentStreamedCode() {
ApplicationManager.getApplication().invokeAndWait {
val disposable = Disposer.newDisposable()
try {
val responseBody = ChatMessageResponseBody(project, false, disposable)
responseBody.updateMessage("```kotlin\nfun main() {\n")
responseBody.addToolStatusPanel(JPanel())
responseBody.updateMessage(" println(\"x\")\n}\n```")
val editorPanels = findComponents(responseBody, ResponseEditorPanel::class.java)
assertThat(editorPanels).hasSize(2)
assertThat(editorPanels.first().getEditor()?.document?.text)
.contains("fun main() {")
.doesNotContain("println(\"x\")")
assertThat(editorPanels.last().getEditor()?.document?.text)
.contains("println(\"x\")")
.contains("}")
.doesNotContain("fun main() {")
val textPanes = findComponents(responseBody, JTextPane::class.java)
assertThat(textPanes).isEmpty()
editorPanels.forEach { panel ->
panel.getEditor()?.let { editor ->
EditorFactory.getInstance().releaseEditor(editor)
}
}
} finally {
Disposer.dispose(disposable)
}
}
}
private fun <T : Component> findComponents(root: Component, type: Class<T>): List<T> {
val matches = mutableListOf<T>()
fun visit(component: Component) {
if (type.isInstance(component)) {
matches.add(type.cast(component))
}
if (component is Container) {
component.components.forEach(::visit)
}
}
visit(root)
return matches
}
}