From bcfb55c52d3bea9df4c11c458eb746c418090c37 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 14 Nov 2025 10:42:24 +0000
Subject: [PATCH 1/7] Initial plan
From fc61b3474271e20d1b7e6f8e0b629b149ad58bbc Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 14 Nov 2025 10:47:25 +0000
Subject: [PATCH 2/7] Fix IDE plugin UI: dark mode support and markdown
rendering
Co-authored-by: Mte90 <403283+Mte90@users.noreply.github.com>
---
.../com/picocode/PicoCodeToolWindowContent.kt | 77 ++++++++++++++++---
1 file changed, 68 insertions(+), 9 deletions(-)
diff --git a/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt b/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt
index 94bb050..83e2d02 100644
--- a/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt
+++ b/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt
@@ -4,10 +4,12 @@ import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.Project
import com.intellij.ui.components.JBScrollPane
import com.intellij.ui.components.JBTextArea
+import com.intellij.ui.JBColor
import java.awt.BorderLayout
import java.awt.Dimension
import java.awt.FlowLayout
import javax.swing.*
+import javax.swing.text.html.HTMLEditorKit
import java.net.HttpURLConnection
import java.net.URL
import com.google.gson.Gson
@@ -157,24 +159,76 @@ class PicoCodeToolWindowContent(private val project: Project) {
return panel
}
+ /**
+ * Convert markdown to HTML for rendering
+ */
+ private fun markdownToHtml(markdown: String): String {
+ var html = markdown
+ // Escape HTML special characters first
+ .replace("&", "&")
+ .replace("<", "<")
+ .replace(">", ">")
+ // Code blocks (```)
+ .replace(Regex("```([\\s\\S]*?)```"), "
$1
")
+ // Inline code (`)
+ .replace(Regex("`([^`]+)`"), "$1")
+ // Bold (**text**)
+ .replace(Regex("\\*\\*([^*]+)\\*\\*"), "$1")
+ // Italic (*text*)
+ .replace(Regex("\\*([^*]+)\\*"), "$1")
+ // Headers
+ .replace(Regex("^### (.+)$", RegexOption.MULTILINE), "$1
")
+ .replace(Regex("^## (.+)$", RegexOption.MULTILINE), "$1
")
+ .replace(Regex("^# (.+)$", RegexOption.MULTILINE), "$1
")
+ // Lists
+ .replace(Regex("^- (.+)$", RegexOption.MULTILINE), "$1")
+ .replace(Regex("^\\* (.+)$", RegexOption.MULTILINE), "$1")
+ // Line breaks
+ .replace("\n", "
")
+
+ // Wrap lists in tags
+ html = html.replace(Regex("(- .*?
)+")) { matchResult ->
+ ""
+ }
+
+ return "$html"
+ }
+
private fun renderChatHistory() {
chatPanel.removeAll()
for ((index, msg) in chatHistory.withIndex()) {
val messagePanel = JPanel(BorderLayout())
+
+ // Use theme-aware colors
+ val borderColor = if (msg.sender == "You")
+ JBColor.BLUE
+ else
+ JBColor.GRAY
+
messagePanel.border = BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(5, 5, 5, 5),
- BorderFactory.createLineBorder(if (msg.sender == "You") java.awt.Color.BLUE else java.awt.Color.GRAY, 1)
+ BorderFactory.createLineBorder(borderColor, 1)
)
- val textArea = JBTextArea(msg.message)
- textArea.isEditable = false
- textArea.lineWrap = true
- textArea.wrapStyleWord = true
- textArea.background = if (msg.sender == "You") java.awt.Color(230, 240, 255) else java.awt.Color.WHITE
+ // Use JEditorPane for HTML/Markdown rendering
+ val editorPane = JEditorPane()
+ editorPane.contentType = "text/html"
+ editorPane.editorKit = HTMLEditorKit()
+ editorPane.text = markdownToHtml(msg.message)
+ editorPane.isEditable = false
+ editorPane.isOpaque = true
+
+ // Use theme-aware background colors
+ editorPane.background = if (msg.sender == "You")
+ JBColor.namedColor("EditorPane.inactiveBackground", JBColor(0xE6F0FF, 0x2D3239))
+ else
+ JBColor.namedColor("EditorPane.background", JBColor.background())
+
+ editorPane.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, true)
val headerPanel = JPanel(BorderLayout())
- headerPanel.add(JLabel("[$msg.sender]"), BorderLayout.WEST)
+ headerPanel.add(JLabel("[${msg.sender}]"), BorderLayout.WEST)
// Add delete button for each message
val deleteBtn = JButton("×")
@@ -186,7 +240,11 @@ class PicoCodeToolWindowContent(private val project: Project) {
headerPanel.add(deleteBtn, BorderLayout.EAST)
messagePanel.add(headerPanel, BorderLayout.NORTH)
- messagePanel.add(textArea, BorderLayout.CENTER)
+
+ // Wrap editorPane in a scroll pane for long messages
+ val messageScrollPane = JBScrollPane(editorPane)
+ messageScrollPane.border = null
+ messagePanel.add(messageScrollPane, BorderLayout.CENTER)
// Add context information if available
if (msg.contexts.isNotEmpty()) {
@@ -196,7 +254,8 @@ class PicoCodeToolWindowContent(private val project: Project) {
}
val contextArea = JBTextArea(contextText.toString())
contextArea.isEditable = false
- contextArea.background = java.awt.Color(250, 250, 250)
+ // Use theme-aware background for context
+ contextArea.background = JBColor.namedColor("Panel.background", JBColor(0xFAFAFA, 0x3C3F41))
messagePanel.add(contextArea, BorderLayout.SOUTH)
}
From 43de00a493771501a0f381c1833484e51f33bda5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 14 Nov 2025 10:48:49 +0000
Subject: [PATCH 3/7] Improve markdown rendering to properly escape HTML and
support dark mode
Co-authored-by: Mte90 <403283+Mte90@users.noreply.github.com>
---
.../com/picocode/PicoCodeToolWindowContent.kt | 62 ++++++++++++++++---
1 file changed, 52 insertions(+), 10 deletions(-)
diff --git a/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt b/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt
index 83e2d02..2f0e32c 100644
--- a/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt
+++ b/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt
@@ -161,17 +161,38 @@ class PicoCodeToolWindowContent(private val project: Project) {
/**
* Convert markdown to HTML for rendering
+ * Note: Code block backgrounds use light gray which may need adjustment for dark themes
*/
private fun markdownToHtml(markdown: String): String {
var html = markdown
- // Escape HTML special characters first
+
+ // Process markdown constructs before escaping HTML
+ // Code blocks (```) - preserve content as-is
+ val codeBlockPlaceholders = mutableListOf()
+ html = html.replace(Regex("```([\\s\\S]*?)```")) { matchResult ->
+ val content = matchResult.groupValues[1]
+ val placeholder = "###CODEBLOCK${codeBlockPlaceholders.size}###"
+ codeBlockPlaceholders.add(content)
+ placeholder
+ }
+
+ // Inline code (`) - preserve content
+ val inlineCodePlaceholders = mutableListOf()
+ html = html.replace(Regex("`([^`]+)`")) { matchResult ->
+ val content = matchResult.groupValues[1]
+ val placeholder = "###INLINECODE${inlineCodePlaceholders.size}###"
+ inlineCodePlaceholders.add(content)
+ placeholder
+ }
+
+ // Escape HTML special characters in remaining text
+ html = html
.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
- // Code blocks (```)
- .replace(Regex("```([\\s\\S]*?)```"), "$1
")
- // Inline code (`)
- .replace(Regex("`([^`]+)`"), "$1")
+
+ // Apply markdown formatting
+ html = html
// Bold (**text**)
.replace(Regex("\\*\\*([^*]+)\\*\\*"), "$1")
// Italic (*text*)
@@ -183,14 +204,35 @@ class PicoCodeToolWindowContent(private val project: Project) {
// Lists
.replace(Regex("^- (.+)$", RegexOption.MULTILINE), "- $1
")
.replace(Regex("^\\* (.+)$", RegexOption.MULTILINE), "- $1
")
- // Line breaks
- .replace("\n", "
")
- // Wrap lists in tags
- html = html.replace(Regex("(- .*?
)+")) { matchResult ->
- ""
+ // Restore code blocks with proper styling
+ codeBlockPlaceholders.forEachIndexed { index, content ->
+ val escapedContent = content
+ .replace("&", "&")
+ .replace("<", "<")
+ .replace(">", ">")
+ html = html.replace("###CODEBLOCK${index}###",
+ "
$escapedContent
")
}
+ // Restore inline code with proper styling
+ inlineCodePlaceholders.forEachIndexed { index, content ->
+ val escapedContent = content
+ .replace("&", "&")
+ .replace("<", "<")
+ .replace(">", ">")
+ html = html.replace("###INLINECODE${index}###",
+ "$escapedContent")
+ }
+
+ // Wrap consecutive list items in tags
+ html = html.replace(Regex("(- .*?
(?:
)?)+")) { matchResult ->
+ "${matchResult.value.replace("
", "")}
"
+ }
+
+ // Convert line breaks (but not inside pre/code tags)
+ html = html.replace("\n", "
")
+
return "$html"
}
From 61fd6a1a13d8321da3e3a4b7b8957a8e350434d3 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 14 Nov 2025 11:40:43 +0000
Subject: [PATCH 4/7] Fix font size and prevent horizontal scrollbar in chat
messages
Co-authored-by: Mte90 <403283+Mte90@users.noreply.github.com>
---
.../kotlin/com/picocode/PicoCodeToolWindowContent.kt | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt b/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt
index 2f0e32c..ce05419 100644
--- a/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt
+++ b/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt
@@ -233,7 +233,7 @@ class PicoCodeToolWindowContent(private val project: Project) {
// Convert line breaks (but not inside pre/code tags)
html = html.replace("\n", "
")
- return "$html"
+ return "$html"
}
private fun renderChatHistory() {
@@ -261,6 +261,9 @@ class PicoCodeToolWindowContent(private val project: Project) {
editorPane.isEditable = false
editorPane.isOpaque = true
+ // Set preferred size to prevent excessive growth
+ editorPane.preferredSize = null
+
// Use theme-aware background colors
editorPane.background = if (msg.sender == "You")
JBColor.namedColor("EditorPane.inactiveBackground", JBColor(0xE6F0FF, 0x2D3239))
@@ -286,6 +289,10 @@ class PicoCodeToolWindowContent(private val project: Project) {
// Wrap editorPane in a scroll pane for long messages
val messageScrollPane = JBScrollPane(editorPane)
messageScrollPane.border = null
+ messageScrollPane.horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER
+ messageScrollPane.verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED
+ // Set maximum height to prevent messages from becoming too tall
+ messageScrollPane.maximumSize = Dimension(Integer.MAX_VALUE, 300)
messagePanel.add(messageScrollPane, BorderLayout.CENTER)
// Add context information if available
From 5a4a4a63aec3a051a87a5e96b7b667236689e455 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 14 Nov 2025 11:47:35 +0000
Subject: [PATCH 5/7] Fix horizontal wrapping: use WrappingEditorPane to track
viewport width
Co-authored-by: Mte90 <403283+Mte90@users.noreply.github.com>
---
.../com/picocode/PicoCodeToolWindowContent.kt | 16 ++++++++++------
1 file changed, 10 insertions(+), 6 deletions(-)
diff --git a/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt b/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt
index ce05419..8230524 100644
--- a/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt
+++ b/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt
@@ -16,6 +16,13 @@ import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.JsonArray
+/**
+ * Custom JEditorPane that tracks viewport width for proper HTML wrapping
+ */
+class WrappingEditorPane : JEditorPane() {
+ override fun getScrollableTracksViewportWidth(): Boolean = true
+}
+
/**
* PicoCode RAG Chat Window
* Simple chat interface that communicates with PicoCode backend
@@ -233,7 +240,7 @@ class PicoCodeToolWindowContent(private val project: Project) {
// Convert line breaks (but not inside pre/code tags)
html = html.replace("\n", "
")
- return "$html"
+ return "$html"
}
private fun renderChatHistory() {
@@ -253,17 +260,14 @@ class PicoCodeToolWindowContent(private val project: Project) {
BorderFactory.createLineBorder(borderColor, 1)
)
- // Use JEditorPane for HTML/Markdown rendering
- val editorPane = JEditorPane()
+ // Use JEditorPane for HTML/Markdown rendering with proper width tracking
+ val editorPane = WrappingEditorPane()
editorPane.contentType = "text/html"
editorPane.editorKit = HTMLEditorKit()
editorPane.text = markdownToHtml(msg.message)
editorPane.isEditable = false
editorPane.isOpaque = true
- // Set preferred size to prevent excessive growth
- editorPane.preferredSize = null
-
// Use theme-aware background colors
editorPane.background = if (msg.sender == "You")
JBColor.namedColor("EditorPane.inactiveBackground", JBColor(0xE6F0FF, 0x2D3239))
From 4da35596b651ce8c8b3ca0258f24fd58b7b7328e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 14 Nov 2025 13:50:26 +0000
Subject: [PATCH 6/7] Add word-wrapping CSS to prevent horizontal overflow in
chat messages
Co-authored-by: Mte90 <403283+Mte90@users.noreply.github.com>
---
.../com/picocode/PicoCodeToolWindowContent.kt | 20 ++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)
diff --git a/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt b/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt
index 8230524..9ee555a 100644
--- a/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt
+++ b/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt
@@ -21,6 +21,16 @@ import com.google.gson.JsonArray
*/
class WrappingEditorPane : JEditorPane() {
override fun getScrollableTracksViewportWidth(): Boolean = true
+
+ override fun getPreferredSize(): Dimension {
+ // Ensure the preferred width doesn't exceed the parent's width
+ val preferredSize = super.getPreferredSize()
+ val parent = parent
+ if (parent != null && parent.width > 0) {
+ preferredSize.width = parent.width
+ }
+ return preferredSize
+ }
}
/**
@@ -212,24 +222,24 @@ class PicoCodeToolWindowContent(private val project: Project) {
.replace(Regex("^- (.+)$", RegexOption.MULTILINE), "- $1
")
.replace(Regex("^\\* (.+)$", RegexOption.MULTILINE), "- $1
")
- // Restore code blocks with proper styling
+ // Restore code blocks with proper styling and wrapping
codeBlockPlaceholders.forEachIndexed { index, content ->
val escapedContent = content
.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
html = html.replace("###CODEBLOCK${index}###",
- "
$escapedContent
")
+ "$escapedContent
")
}
- // Restore inline code with proper styling
+ // Restore inline code with proper styling and wrapping
inlineCodePlaceholders.forEachIndexed { index, content ->
val escapedContent = content
.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
html = html.replace("###INLINECODE${index}###",
- "$escapedContent")
+ "$escapedContent")
}
// Wrap consecutive list items in tags
@@ -240,7 +250,7 @@ class PicoCodeToolWindowContent(private val project: Project) {
// Convert line breaks (but not inside pre/code tags)
html = html.replace("\n", "
")
- return "$html"
+ return "$html"
}
private fun renderChatHistory() {
From be3e58b0448025f613bd54dc8c1a3c0dc690ceeb Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 14 Nov 2025 14:06:40 +0000
Subject: [PATCH 7/7] Improve width handling: enhance WrappingEditorPane and
set messagePanel max width
Co-authored-by: Mte90 <403283+Mte90@users.noreply.github.com>
---
.../com/picocode/PicoCodeToolWindowContent.kt | 23 ++++++++++++++++---
1 file changed, 20 insertions(+), 3 deletions(-)
diff --git a/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt b/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt
index 9ee555a..ced90a0 100644
--- a/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt
+++ b/ide-plugins/src/main/kotlin/com/picocode/PicoCodeToolWindowContent.kt
@@ -23,14 +23,28 @@ class WrappingEditorPane : JEditorPane() {
override fun getScrollableTracksViewportWidth(): Boolean = true
override fun getPreferredSize(): Dimension {
- // Ensure the preferred width doesn't exceed the parent's width
+ // Let the parent determine the width, we only care about height
val preferredSize = super.getPreferredSize()
+
+ // If we're in a scroll pane, use the viewport width
val parent = parent
- if (parent != null && parent.width > 0) {
- preferredSize.width = parent.width
+ if (parent is JViewport) {
+ val viewportWidth = parent.width
+ if (viewportWidth > 0) {
+ // Set a temporary size to calculate the proper height
+ setSize(viewportWidth, Int.MAX_VALUE)
+ preferredSize.width = viewportWidth
+ preferredSize.height = super.getPreferredSize().height
+ }
}
return preferredSize
}
+
+ override fun getMaximumSize(): Dimension {
+ val maxSize = super.getMaximumSize()
+ maxSize.width = Integer.MAX_VALUE
+ return maxSize
+ }
}
/**
@@ -259,6 +273,9 @@ class PicoCodeToolWindowContent(private val project: Project) {
for ((index, msg) in chatHistory.withIndex()) {
val messagePanel = JPanel(BorderLayout())
+ // Ensure messagePanel respects the container width
+ messagePanel.maximumSize = Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE)
+
// Use theme-aware colors
val borderColor = if (msg.sender == "You")
JBColor.BLUE